Hadoop MR Shuffle

shuffle 的过程是在 MapTask 之后 reducerTask 之前的这么一段对数据处理传递的过程

分区

mapTask 执行数据操作后, 将输出数据存储到 环形缓冲区 中, 当环形缓冲区内数据量达到最大量(默认 100M)的 80%时, 将内部数据溢写到磁盘中存储,然后环形缓冲区再进行反向写入剩余数据; 写入磁盘时会对数据进行分区,默认分区为 0(不分区),分区数,会影响最终reduceTask 写入的文件数

使用场景-希望将分析后的数据根据不同的属性, 写到多个文件中

例如: 分析 2020 年疫情, 各省市区疫情情况, 将分析结果按照省份分别写入不同的文件

  • 自定义分区器

    驱动类设置自定义分区器生效

            job.setNumReduceTasks(3);//reduceTasks 数不能小于分区数
            job.setPartitionerClass(CustomerPartitioner.class;
    

    实现自定义分区器

    1. 继承Partitioner抽象类
    2. 指定泛型 key- value 为 mapper 输出 key-value 类型
    3. 实现方法 getPartition 内部是分区逻辑
    /**
     * <h3>study-all</h3>
     *
     * <p>自定义分区器实现</p>
     *
     * @Author zcz
     * @Date 2020-04-05 11:29
     */
    public class CustomerPartitioner extends Partitioner<Comparable, Writable> {
        @Override
        public int getPartition(WritableComparable key, Writable value, int i) {
    
            job.setNumReduceTasks();
            job.setPartitionerClass();
            //key 是 mapper 的输出 key
            //value 是 mapper 的输出 value
            //根据具体需求完成分区,
            //返回值是分区位置, 例如三个分区0,1,2   逻辑上应该是分区 0 的 return 0; 应该是分区 1 的 return 1; 应该是分区 2 的 return 2;
            return 0;
        }
    }
    

排序

  • 分类

    部分排序: 各分区进行排序

    全排序: 保证最后输出只有一个文件, 并且内部是有顺序的

    辅助排序(分组排序):在 reduce 阶段, 对 key 进行一定规则的分组排序

    二次排序: 排序方法 compareTo 中的判断条件为两个以上

    MapTask和 reduceTask 都会对输入数据的 key 进行排序, 默认使用字典顺序排序, 算法使用快排

  • MapReduce 过程中的排序

    上面分区中描述到, mapTask将输出数据写入到 环形缓冲区 中, 当缓冲区中的数据达到阈值后, 会对缓冲区内的数据进行分区计算, 并进行分区内排序(默认字典顺序快排), 然后将缓冲区内经过分区计算的数据溢写到磁盘中存储, 在 mapper 阶段会有多个 MapTask执行, 所以会有多组分区, (可选操作)当所有 MapTask 执行完后, 将所有mapTask 的分区, 按各个分区规则进行归并排序

    mapTask执行结束后, reduceTask 阶段会到 mapTask 下读取各组分区数据到reduceTask 内存中, 当读入的文件大小到达一定的阈值后, 会将文件溢写到磁盘中, 当磁盘中的文件达到一定数量, 会对数据进行按分区归并排序, 当所有数据拷贝完后,reduceTask 会将内存中和磁盘中的数据进行按分区归并排序

  • 自定义排序

    首先说在排序中都是对输入或输出的 key 进行排序的, 所有 key 都需要是可排序的, 实现 WritableComparable接口, 实现 compareTo 方法, 就可以排序了

    /**
     * <h3>study-all</h3>
     * <p>自定义可排序类</p>
     *
     * @Author zcz
     * @Date 2020-04-05 18:51
     */
    public class CustomerCompareBean implements WritableComparable<CustomerCompareBean> {
        private Integer column1;
        private Integer column2;
    
        public CustomerCompareBean(Integer column1, Integer column2) {
            this.column1 = column1;
            this.column2 = column2;
        }
    
        public CustomerCompareBean() {
        }
    
        public Integer getColumn1() {
            return column1;
        }
    
        public void setColumn1(Integer column1) {
            this.column1 = column1;
        }
    
        public Integer getColumn2() {
            return column2;
        }
    
        public void setColumn2(Integer column2) {
            this.column2 = column2;
        }
    
        @Override
        public void write(DataOutput out) throws IOException {
            out.writeInt(column1);
            out.writeInt(column2);
        }
    
        @Override
        public void readFields(DataInput in) throws IOException {
            this.column1 = in.readInt();
            this.column2 = in.readInt();
        }
        
    
    		/**
         * 实现比较方法可进行排序
         * */
        @Override
        public int compareTo(CustomerCompareBean o) {
            
            return (this.column1 != o.getColumn1())?
                    (this.column1 - o.getColumn1()):
                    (this.column2 - o.getColumn2());
        }
    }
    
  • 全排序: 只需要 mapper 输出 key 和 reducer 读取 key 可排序

  • 分区排序: 在全排序的基础上, 增加分区设置后可分区排序(自定义分区见分区章节)

  • 分组排序

    • 自定义分组排序类CustomerGroupingComparator继承 WritableComparator,

    • 重写compare 方法

      @Override
      public int compare(WritableComparable a, WritableComparable b) {
      		// 比较的业务逻辑
      		return result;
      }
      
    • 重写无参构造方法, 调用 super(OrderBean.class, , true)

      protected OrderGroupingComparator() {
      		super(OrderBean.class, true);
      }
      

      例:

    public class CustomerGroupingComparator extends WritableComparator {
    	//重写无参构造
    	protected OrderGroupingComparator() {
    		super(OrderBean.class, true);
    	}
    
    	@Override
    	public int compare(WritableComparable a, WritableComparable b) {
    
    		//a与b的比较逻辑
    
    		return result;
    	}
    }
    

    Driver 设置分组排序

    job.setGroupingComparatorClass(OrderGroupingComparator.class);
    

Combiner 合并

  • Combiner 不是必须的

  • Combiner 本质就是 reducer的子类

  • Combiner 和 reducer 的区别是执行位置:

    Combiner 在每个 MapTask 后面执行

    reducer 是在所有 MapTask 后面执行

  • Combiner 的意义是为每个 MapTask 的输出进行汇总, 减少reducerTask读取数据的网络传输量

  • 使用 Combiner 不能影响最终结果

  • Combiner 的key-value输入类型是Mapper的输出类型, Combiner 的输出类型是 Reducer 的输出类型

自定义 Combiner

/**
 * <h3>study-all</h3>
 *
 * <p>自定义 Combiner</p>
 *
 * @Author zcz
 * @Date 2020-04-05 19:27
 */
public class CustomerCombiner extends Reducer<Text, IntWritable, Text, IntWritable> {
    private IntWritable value = new IntWritable();

    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
        int sum = 0;
        for (IntWritable i : values) {
            sum = sum+ i.get();
        }
        value.set(sum);
        context.write(key, value);

    }
}

Driver 中设置启动 Combiner

conf.setConbinerClass(CustomerCombiner.class);

Shuffle 过程总结

  1. MapTask 对数据进行读取, 并操作数据对数据进行整理后, 将整理后的数据调用 Partitioner 进行数据分区, 将分区后的数据写入环形缓冲区环形缓冲区内对数据分区排序, 并对区内部数据进行以 key 值排序(快排)
  2. 当数据缓冲区内数据达到阈值(100M*80%), 如果设置了 Combiner, 先对分区数据执行 Combiner 合并, 然后将各分区合并后的数据溢写到磁盘(分区数据, 区内有序)
  3. 如果有需要可以对写入到磁盘的分区数据进行 Combiner 操作, 并执行归并排序后再写入磁盘
  4. 将分区数据的元信息写到内存索引数据结构SpillRecord中,其中每个分区的元信息包括在临时文件中的偏移量、压缩前数据大小和压缩后数据大小。如果当前内存索引大小超过1MB,则将内存索引写到文件output/spillN.out.index中
  5. 如果设置了 Combiner , mapTask对所有数据进行一次 Combiner 合并, 保证每个 mapTask 只生成一个文件
  6. mapTask 全部结束后开始启动执行 reducerTask
  7. reducerTask将 mapTask生成的数据拷贝到 reducerTask 执行节点内存中,如果拷贝数据超过阈值, 将数据写到磁盘中
  8. reducerTask 将内存中和磁盘中的数据进行一次合并, 防止内存中数据过多, 和防止磁盘中文件过多
  9. reducerTask 将数据进行一次全局排序(归并)
  10. shuffle 过程结束, reducer 将处理数据写到 HDFS

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值