MapReduce框架原理之shuff机制(2)

MapReduce 框架原理之排序

WritableComparable排序

  1. 概述

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

  2. 注意

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

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

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

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

  3. 排序分类

    • 部分排序

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

    • 全排序

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

    • 辅助排序(GroupComparator 分组)

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

    • 二次排序

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

  4. 自定义排序 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	13736230513	192.196.100.1	www.atguigu.com	2481	24681	200
      2	13846544121	192.196.100.2			264	0	200
      3 	13956435636	192.196.100.3			132	1512	200
      4 	13966251146	192.168.100.1			240	0	404
      5 	18271575951	192.168.100.2	www.atguigu.com	1527	2106	200
      6 	84188413	192.168.100.3	www.atguigu.com	4116	1432	200
      7 	13590439668	192.168.100.4			1116	954	200
      8 	15910133277	192.168.100.5	www.hao123.com	3156	2936	200
      9 	13729199489	192.168.100.6			240	0	200
      10 	13630577991	192.168.100.7	www.shouhu.com	6960	690	200
      11 	15043685818	192.168.100.8	www.baidu.com	3659	3538	200
      12 	15959002129	192.168.100.9	www.atguigu.com	1938	180	500
      13 	13560439638	192.168.100.10			918	4938	200
      14 	13470253144	192.168.100.11			180	180	200
      15 	13682846555	192.168.100.12	www.qq.com	1938	2910	200
      16 	13992314666	192.168.100.13	www.gaga.com	3008	3720	200
      17 	13509468723	192.168.100.14	www.qinghua.com	7335	110349	404
      18 	18390173782	192.168.100.15	www.sogou.com	9531	2412	200
      19 	13975057813	192.168.100.16	www.baidu.com	11058	48243	200
      20 	13768778790	192.168.100.17			120	120	200
      21 	13568436656	192.168.100.18	www.alibaba.com	2481	24681	200
      22 	13568436656	192.168.100.19			1116	954	200
      
    • 期望输出数据

      13509468723	7335	110349	117684
      13736230513	2481	24681	27162
      13956435636	132		1512	1644
      13846544121	264		0		264
      
    • 需求分析

    • 代码实现

      1. FlowBean 对象在在需求 1 基础上增加了比较功能

        package com.atguigu.mapreduce.writablecompable;

        import org.apache.hadoop.io.WritableComparable;
        import java.io.DataInput;
        import java.io.DataOutput;
        import java.io.IOException;

        public class FlowBean implements WritableComparable<FlowBean{

            private long upFlow; //上行流量
            private long downFlow; //下行流量
            private long sumFlow; //总流量

            //提供无参构造
            public FlowBean() {
            }

            //生成三个属性的getter和setter方法
            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 void write(DataOutput out) throws IOException {
                out.writeLong(this.upFlow);
                out.writeLong(this.downFlow);
                out.writeLong(this.sumFlow);

            }

            @Override
            public void readFields(DataInput in) throws IOException {
                this.upFlow = in.readLong();
                this.downFlow = in.readLong();
                this.sumFlow = in.readLong();
            }

            //重写ToString,最后要输出FlowBean
            @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;
                }
            }
        }
      2. 编写 Mapper 类

        package com.atguigu.mapreduce.writablecompable;

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

        public class FlowMapper extends Mapper<LongWritableTextFlowBeanText{
            private FlowBean outK = new FlowBean();
            private Text outV = new Text();

            @Override
            protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {

                //1 获取一行数据
                String line = value.toString();

                //2 按照"\t",切割数据
                String[] split = line.split("\t");

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

                //4 写出outK outV
                context.write(outK,outV);
            }
        }
      3. 编写 Reducer 类

        package com.atguigu.mapreduce.writablecompable;

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

        public class FlowReducer extends Reducer<FlowBeanTextTextFlowBean{
            @Override
            protected void reduce(FlowBean key, Iterable<Text> values, Context context) throws IOException, InterruptedException {

                //遍历values集合,循环写出,避免总流量相同的情况
                for (Text value : values) {
                    //调换KV位置,反向写出
                    context.write(value,key);
                }
            }
        }
      4. 编写 Driver 类

        package com.atguigu.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;

        public class FlowDriver {

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

                //1 获取job对象
                Configuration conf = new Configuration();
                Job job = Job.getInstance(conf);

                //2 关联本Driver类
                job.setJarByClass(FlowDriver.class);

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

                //4 设置Map端输出数据的KV类型
                job.setMapOutputKeyClass(FlowBean.class);
                job.setMapOutputValueClass(Text.class);

                //5 设置程序最终输出的KV类型
                job.setOutputKeyClass(Text.class);
                job.setOutputValueClass(FlowBean.class);

                //6 设置输入输出路径
                FileInputFormat.setInputPaths(job, new Path("D:\\inputflow2"));
                FileOutputFormat.setOutputPath(job, new Path("D:\\comparout"));

                //7 提交Job
                boolean b = job.waitForCompletion(true);
                System.exit(b ? 0 : 1);
            }
        }
  6. WritableComparable 排序案例实操(区内排序)

    1. 需求

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

    2. 需求分析

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

    3. 期望输出

    4. 案例实操

      • 增加自定义分区类

        package com.atguigu.mapreduce.partitionercompable;

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

        public class ProvincePartitioner2 extends Partitioner<FlowBeanText{

            @Override
            public int getPartition(FlowBean flowBean, Text text, int numPartitions) {
                //获取手机号前三位
                String phone = text.toString();
                String prePhone = phone.substring(03);

                //定义一个分区号变量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;
                }

                //最后返回分区号partition
                return partition;
            }
        }
      1. 在驱动类中添加分区类

        // 设置自定义分区器
        job.setPartitionerClass(ProvincePartitioner2.class);

        // 设置对应的ReduceTask的个数
        job.setNumReduceTasks(5);

Combiner 合并

  1. Combiner 合并

    • Combiner 是 MR 程序中 Mapper 和 Reducer 之外的一种组件

    • Combiner 组件的父类就是 Reducer

    • Combiner 和 Reducer 的区别在于运行的位置

      Combiner 是在每一个 MapTask 所在的节点运行

      Reducer 是接收全局所有 Mapper 的输出结果

    • Combiner 的意义就是对每一个 MapTask 的输出进行局部汇总,以减小网络传输量

    • Combiner 能够应用的前提是不能影响最终的业务逻辑,而且,Combiner 的输出 KV 应该跟 Reducer 的输入 KV 类型要对应起来。

      Mapper

      3 5 7 -> (3+5+7)/3 =5

      2 6 ->( 2 + 6)

      Reducer

      (3 + 5 +7 + 2 + 6) / 5 = 23 / 5 不等于 (5 + 4) / 2 = 9 /2

    1. 自定义 Combiner 实现步骤

      • 自定义一个 Combiner 继承 Reducer,重写 Reduce 方法

        public class WordCountCombiner extends Reducer<TextIntWritableTextIntWritable{

            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);
  2. Combiner 合并案例实操

    • 需求

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

    • 数据输入

      banzhang ni hao xihuan hadoop banzhang banzhang ni hao xihuan hadoop banzhang

    • 期望输出数据

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

    • 需求分析

    • 案例实操

      • 方法一:增加一个 WordCountCombiner 类继承 Reducer

        package com.atguigu.mapreduce.combiner;

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

        public class WordCountCombiner extends Reducer<TextIntWritableTextIntWritable{

        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();
                }

                //封装outKV
                outV.set(sum);

                //写出outKV
                context.write(key,outV);
            }
        }
        //在WordcountDriver驱动类中指定Combiner

        // 指定需要使用combiner,以及用哪个类作为combiner的逻辑

        job.setCombinerClass(WordCountCombiner.class);
      • 方案二:将 WordcountReducer 作为 Combiner 在 WordcountDriver 驱动类中指定

        // 指定需要使用Combiner,以及用哪个类作为Combiner的逻辑
        job.setCombinerClass(WordCountReducer.class);
posted @ 2021-11-06 11:27  逆十字  阅读(143)  评论(0)    收藏  举报