Hadoop 3.x(MapReduce)----【MapReduce 框架原理 二】

Hadoop 3.x(MapReduce)----【MapReduce 框架原理 二】

  • 1. MapReduce工作流程
  • 2. Shuffle机制
    • 1. Shuffle机制
    • 2. Partition分区
      • 1. 问题引出
      • 2. 默认Partition分区
      • 3. 自定义Partition步骤
      • 4. 分区总结
      • 5. 案例分析
    • 3. Partition分区案例实操
      • 1. 需求
      • 2. 需求分析
      • 3. 在之前的案例基础上,增加一个分区类
      • 4. 在驱动函数中增加自定义数据分区设置和ReduceTask设置
      • 5. 测试输出
    • 4. WritableComparable排序
      • 1. 排序概述
      • 2. 排序分类
      • 3. 自定义排序 WritableComparable 原理分析
    • 5. WritableComparable排序案例实操(全排序)
      • 1. 需求
      • 2. 需求分析
      • 3. 代码实现
    • 6. WritableComparable排序案例实操(区内排序)
      • 1. 需求
      • 2. 需求分析
      • 3. 案例实操
    • 7. Combiner合并
    • 8. Combiner合并案例实操
      • 1. 需求
      • 2. 需求分析
      • 3. 案例实操-方案一
      • 4. 案例实操-方案二
        • 5. 测试结果

1. MapReduce工作流程

Hadoop 3.x(MapReduce)----【MapReduce 框架原理 二】_第1张图片
Hadoop 3.x(MapReduce)----【MapReduce 框架原理 二】_第2张图片

上面流程是整个 MapReduce 最全工作流程,但是 Shuffle 过程只是从第 7 步开始到第 16 步结束,具体 Shuffle 过程详解,如下:

  1. MapTask 手机我们的 map() 方法输出的 kv 对,放到内存缓冲区
  2. 从内存缓冲区不断溢出本地磁盘文件,可能会溢出多个文件
  3. 多个溢出文件会被合并成大的溢出文件
  4. 在溢出过程及合并过程中,都有调用 Partitioner 进行分区和针对 key 进行排序
  5. ReduceTask 根据自己的分区号,去各个 MapTask 机器上取相应的结果分区数据
  6. ReduceTask 会抓取到同一个分区来自不同 MapTask 的结果文件,ReduceTask 会将这些文件再进行合并(归并排序)
  7. 合并成大文件后,Shuffle 的过程也就结束了,后面进入 ReduceTask 的逻辑运算过程(从文件中取出一个一个的键值对 Group,调用用户自定义的 reduce() 方法)

注意

  1. Shuffle 中的缓冲区大小会影响到 MapReduce 程序的执行效率,原则上说,缓冲区越大,磁盘 io 的次数越少,执行速度就越快。
  2. 缓冲区的大小可以通过参数调整,参数:mapreduce.task.io.sort.mb 默认 100M。

2. Shuffle机制

1. Shuffle机制

Map 方法之后,Reduce 方法之前的数据处理过程称之为 Shuffle。

Hadoop 3.x(MapReduce)----【MapReduce 框架原理 二】_第3张图片

2. Partition分区

1. 问题引出

要求将统计结构按照条件输出到不同文件中(分区)。比如:将统计结构按照手机归属地不同省份输出到不同文件中(分区)

2. 默认Partition分区

Hadoop 3.x(MapReduce)----【MapReduce 框架原理 二】_第4张图片

默认分区是根据 key 的 hashCode 对 ReduceTasks 个数取模得到的。用户没法控制哪个 key 存储到哪个分区。

3. 自定义Partition步骤

  1. 自定义类继承 Partition,重写 getPartition() 方法

Hadoop 3.x(MapReduce)----【MapReduce 框架原理 二】_第5张图片

  1. 在 Job 驱动中,设置自定义 Partition

在这里插入图片描述

  1. 自定义 Partition 后,要根据自定义 Partition 的逻辑设置相应数量的 ReduceTask

在这里插入图片描述

4. 分区总结

  1. 如果 ReduceTask 的数量 > getPartition 的结果数,则会多产生几个空的输出文件 part-r-000xx
  2. 如果 1 < ReduceTask 的数量 < getPartition 的结果数,则有一部分分区数据无处安放,会 Exception
  3. 如果 ReduceTask 的数量 = 1,则不管 MapTask 端输出多少分区文件,最终结果都会交给这一个 ReduceTask,最终也就只会产生一个结果文件 part-r-00000
  4. 分区号必须从零开始,逐一累加。

5. 案例分析

假如:假设自定义分区数为 5,则

  1. job.setNumReduceTask(1);会正常运行,只不过会产生一个输出文件
  2. job.setNumReduceTask(2);会报错
  3. job.setNumReduceTask(6);大于 5,程序会正常运行,会产生空文件

3. Partition分区案例实操

1. 需求

将统计结果按照手机归属地不同省份输出到不同文件中(分区)

  1. 输入数据

Hadoop 3.x(MapReduce)----【MapReduce 框架原理 二】_第6张图片

  1. 期望输出数据

手机号 136、137、138、139 开头都分别放到一个独立的 4 个文件中,其他开头的放到一个文件中。

2. 需求分析

Hadoop 3.x(MapReduce)----【MapReduce 框架原理 二】_第7张图片

3. 在之前的案例基础上,增加一个分区类

package com.fickler.mapreduce.writable;

import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;

/**
 * @author dell
 * @version 1.0
 */
public class ProvincePartitioner extends Partitioner<Text, FlowBean> {


    @Override
    public int getPartition(Text text, FlowBean flowBean, int i) {

        //获取手机号的前三位prePhone
        String phone = text.toString();
        String prePhone = phone.substring(0, 3);

        //定义一个分区号变量partition,根据prePhone设置分区号
        int partition;

        if ("136".equals(prePhone)){
            partition = 0;
        }else if ("137".equals(prePhone)){
            partition = 1;
        } else if ("138".equals(prePhone)) {
            partition = 2;
        } else if ("139".equals(prePhone)) {
            partition = 3;
        }else {
            partition = 4;
        }

        //最后返回分区号
        return partition;

    }
}

4. 在驱动函数中增加自定义数据分区设置和ReduceTask设置

package com.fickler.mapreduce.writable;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;

/**
 * @author dell
 * @version 1.0
 */
public class FlowDriver {

    public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {

        //1.获取job
        Configuration configuration = new Configuration();
        Job job = Job.getInstance(configuration);

        //2.设置jar
        job.setJarByClass(FlowDriver.class);

        //3.关联mapper和Reducer
        job.setMapperClass(FlowMapper.class);
        job.setReducerClass(FlowReducer.class);

        //4.设置mapper输出key和value类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(FlowBean.class);

        //5.设置最终数据输出的key和value类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(FlowBean.class);

        //8.指定自定义分区器
        job.setPartitionerClass(ProvincePartitioner.class);

        //9.同时指定相应数量的ReduceTask
        job.setNumReduceTasks(5);

        //6.设置数据的输入路径和输出路径
        FileInputFormat.setInputPaths(job, new Path("C:\\Users\\dell\\Desktop\\input"));
        FileOutputFormat.setOutputPath(job, new Path("C:\\Users\\dell\\Desktop\\output"));

        //7.提交job
        boolean result = job.waitForCompletion(true);
        System.exit(result ? 0 : 1);

    }

}


Hadoop 3.x(MapReduce)----【MapReduce 框架原理 二】_第8张图片

5. 测试输出

Hadoop 3.x(MapReduce)----【MapReduce 框架原理 二】_第9张图片

4. WritableComparable排序

1. 排序概述

排序是 MapReduce 框架中最重要的操作之一。

MapTask 和 ReduceTask 均会对数据按照 key 进行排序。该操作属于 Hadoop 的默认行为。任何应用程序中的数据均会被排序,而不管逻辑上是否需要

默认排序是按照字典顺序排序,且实现该排序的方法是快速排序

对于 MapTask,它会将处理的结果暂时放到环形缓冲区中,当环形缓冲区使用率到达一定阈值后,再对缓冲区中的数据进行一次快速排序,并将这些有序数据溢写到磁盘上,而当数据处理完毕后,它会对磁盘上所有文件进行归并排序

对于 ReduceTask,它从每个 MapTask 上远程拷贝相应的数据文件,如果文件大小超过一定阈值,则溢写到磁盘上,否则存储在内存中。如果磁盘上文件数目达到一定阈值,则进行一次归并排序以生成一个更大文件;如果内存中文件大小伙子数目超过一定阈值,则进行一次合并后将数据溢写到磁盘上。当所有数据拷贝完毕后,ReduceTask 统一对内存和磁盘上的所有数据进行一次归并排序

2. 排序分类

  1. 部分排序

MapReduce 根据输入记录的键对数据集排序。保证输出的每个文件内部有序

  1. 全排序

最终输出结果只有一个文件,且文件内部有序。实现方式是只设置一个 ReduceTask。但该方法在处理大型文件时效率极低,因为一台机器处理所有文件,完全丧失了 MapReduce 所提供的并行架构。

  1. 辅助排序

在 Reduce 端对 key 进行分组。应用于:在接收的 key 为 bean 对象时,想让一个或几个字段相同(全部字段比较不相同)的 key 进入到同一个 reduce 方法时,可以采用分组排序。

  1. 二次排序

在自定义排序过程中,如果 compareTo 中的判断条件为两个即为二次排序。

3. 自定义排序 WritableComparable 原理分析

bean 对象做为 key 传输,需要实现 WritableComparable 接口重写 compareTo 方法,就可以实现排序。

@Override
public int compareTo(FlowBean bean) {
	int result;
	// 按照总流量大小,倒序排列
	if (this.sumFlow > bean.getSumFlow()) {
		result = -1;
	}else if (this.sumFlow < bean.getSumFlow()) {
		result = 1;
	}else {
		result = 0;
	}
	return result;
}

5. WritableComparable排序案例实操(全排序)

1. 需求

根据上个案例产生的结果,再次对总流量进行倒序排序

  1. 输入数据

Hadoop 3.x(MapReduce)----【MapReduce 框架原理 二】_第10张图片

  1. 期望输出数据

Hadoop 3.x(MapReduce)----【MapReduce 框架原理 二】_第11张图片

2. 需求分析

Hadoop 3.x(MapReduce)----【MapReduce 框架原理 二】_第12张图片

3. 代码实现

  1. FlowBean 对象在需求 1 基础上增加比较功能
package com.fickler.mapreduce.writablecompable;

import org.apache.hadoop.io.WritableComparable;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

/**
 * @author dell
 * @version 1.0
 */
public class FlowBean implements WritableComparable<FlowBean> {

    private long upFlow;
    private long downFlow;
    private long sumFlow;

    public FlowBean() {
    }

    public long getUpFlow() {
        return upFlow;
    }

    public void setUpFlow(long upFlow) {
        this.upFlow = upFlow;
    }

    public long getDownFlow() {
        return downFlow;
    }

    public void setDownFlow(long downFlow) {
        this.downFlow = downFlow;
    }

    public long getSumFlow() {
        return sumFlow;
    }

    public void setSumFlow(long sumFlow) {
        this.sumFlow = sumFlow;
    }

    public void setSumFlow() {
        this.sumFlow = this.upFlow + this.downFlow;
    }

    @Override
    public String toString() {
        return upFlow +
                "\t" + downFlow +
                "\t" + sumFlow;
    }

    @Override
    public int compareTo(FlowBean o) {
        if (this.sumFlow > o.sumFlow){
            return -1;
        } else if (this.sumFlow < o.sumFlow){
            return 1;
        } else {
            return 0;
        }
    }

    @Override
    public void write(DataOutput dataOutput) throws IOException {

        dataOutput.writeLong(this.upFlow);
        dataOutput.writeLong(this.downFlow);
        dataOutput.writeLong(this.sumFlow);

    }

    @Override
    public void readFields(DataInput dataInput) throws IOException {

        this.upFlow = dataInput.readLong();
        this.downFlow = dataInput.readLong();
        this.sumFlow = dataInput.readLong();

    }
}

  1. 编写 Mapper 类
package com.fickler.mapreduce.writablecompable;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

/**
 * @author dell
 * @version 1.0
 */
public class FlowMapper extends Mapper<LongWritable, Text, FlowBean, Text> {

    private FlowBean outK = new FlowBean();
    private Text outV = new Text();

    @Override
    protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, FlowBean, Text>.Context context) throws IOException, InterruptedException {

        String line = value.toString();

        String[] split = line.split("\t");

        outK.setUpFlow(Long.parseLong(split[1]));
        outK.setDownFlow(Long.parseLong(split[2]));
        outK.setSumFlow();
        outV.set(split[0]);

        context.write(outK, outV);

    }
}

  1. 编写 Reducer 类
package com.fickler.mapreduce.writablecompable;

import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

/**
 * @author dell
 * @version 1.0
 */
public class FlowReducer extends Reducer<FlowBean, Text, Text, FlowBean> {

    @Override
    protected void reduce(FlowBean key, Iterable<Text> values, Reducer<FlowBean, Text, Text, FlowBean>.Context context) throws IOException, InterruptedException {

        for (Text value : values){
            context.write(value, key);
        }

    }
}

  1. 编写 Driver 类
package com.fickler.mapreduce.writablecompable;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;

/**
 * @author dell
 * @version 1.0
 */
public class FlowDriver {

    public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {

        Configuration configuration = new Configuration();
        Job job = Job.getInstance(configuration);

        job.setJarByClass(FlowDriver.class);

        job.setMapperClass(FlowMapper.class);
        job.setReducerClass(FlowReducer.class);

        job.setMapOutputKeyClass(FlowBean.class);
        job.setMapOutputValueClass(Text.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(FlowBean.class);

        FileInputFormat.setInputPaths(job, new Path("C:\\Users\\dell\\Desktop\\input"));
        FileOutputFormat.setOutputPath(job, new Path("C:\\Users\\dell\\Desktop\\output"));

        boolean b = job.waitForCompletion(true);
        System.out.println(b ? 0 : 1);

    }

}

  1. 测试输出(这里的输入文件,是上传一个的测试输出样例,不要搞错了)
    Hadoop 3.x(MapReduce)----【MapReduce 框架原理 二】_第13张图片

6. WritableComparable排序案例实操(区内排序)

1. 需求

要求每个省份手机号输出的文件中按照总流量内部排序。

2. 需求分析

基于前一个需求,增加自定分区类,分区按照省份手机号设置。

Hadoop 3.x(MapReduce)----【MapReduce 框架原理 二】_第14张图片

3. 案例实操

  1. 增加自定义分区类
package com.fickler.mapreduce.writablecompable;

import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;

/**
 * @author dell
 * @version 1.0
 */
public class ProvincePartitioner extends Partitioner<FlowBean, Text> {


    @Override
    public int getPartition(FlowBean flowBean, Text text, int i) {

        String phone = text.toString();
        String prePhone = phone.substring(0, 3);

        int partition;
        if ("136".equals(prePhone)){
            partition = 0;
        } else if ("137".equals(prePhone)) {
            partition = 1;
        } else if ("138".equals(prePhone)){
            partition = 2;
        } else if ("139".equals(prePhone)) {
            partition = 3;
        } else {
            partition = 4;
        }

        return partition;

    }
}

  1. 在驱动类中添加分区类
        job.setPartitionerClass(ProvincePartitioner.class);
        job.setNumReduceTasks(5);
  1. 测试输出

Hadoop 3.x(MapReduce)----【MapReduce 框架原理 二】_第15张图片
Hadoop 3.x(MapReduce)----【MapReduce 框架原理 二】_第16张图片

7. Combiner合并

  1. Combiner 是 MR 程序中 Mapper 和 Reducer 之外的一种组件。
  2. Combiner 组件的父类就是 Reducer。
  3. Combiner 和 Reducer 的区别在于运行的位置,Combiner 是在每一个 MapTask 所在的节点运行;Reducer 是接收全局所有 Mapper 的输出结果;
  4. Combiner 的意义就是对每一个 MapTask 的输出进行局部汇总,以减少网络传输量。
  5. Combiner 能够应用的前提是不能影响最终的业务逻辑,而且,Combiner 的输出 kv 应该跟 Reducer 的输入 kv 类型要对应起来。

Hadoop 3.x(MapReduce)----【MapReduce 框架原理 二】_第17张图片

  1. 自定义 Combiner 实现步骤

    • 自定义一个 Combiner 继承 Reducer,重写 Reduce 方法
public class WordCountCombiner extends Reducer<Text, IntWritable, Text, IntWritable> {
	private IntWritable outV = new IntWritable();
 	@Override
 	protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
		int sum = 0;
		for (IntWritable value : values) {
 			sum += value.get();
 		}
 
		outV.set(sum);
 
		context.write(key,outV);
	}
}
  • 在 Job 驱动类中设置
job.setCombinerClass(WordCountCombiner.class);

8. Combiner合并案例实操

1. 需求

统计过程中对每一 MapTask 的输出进行局部汇总,以减少网络传输量即采用 Combiner 功能。

  1. 数据输入

Hadoop 3.x(MapReduce)----【MapReduce 框架原理 二】_第18张图片

  1. 期望输出数据

期望:Combine 输入数据多,输出时经过合并,输出数据降低。

2. 需求分析

Hadoop 3.x(MapReduce)----【MapReduce 框架原理 二】_第19张图片

3. 案例实操-方案一

  1. 增加一个 WordCountCombiner 类继承 Reducer
package com.fickler.mapreduce.wordcount;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

/**
 * @author dell
 * @version 1.0
 */
public class WordCountCombiner extends Reducer<Text, IntWritable, Text, IntWritable> {

    private IntWritable outV = new IntWritable();

    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Reducer<Text, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException {

        int sum = 0;
        for (IntWritable intWritable : values){
            sum += intWritable.get();
        }

        outV.set(sum);

        context.write(key, outV);

    }
}

  1. 在 WordcountDriver 驱动类中指定 Combiner
        job.setCombinerClass(WordCountCombiner.class);

4. 案例实操-方案二

  1. 将 WordcountReducer 作为 Combiner 在 WordcountDriver 驱动类中指定
        job.setCombinerClass(WordCountReducer.class);

5. 测试结果

Hadoop 3.x(MapReduce)----【MapReduce 框架原理 二】_第20张图片

你可能感兴趣的:(Hadoop,hadoop,mapreduce,大数据)