大数据Hadoop-MapReduce学习之旅第三篇

466 阅读6分钟

「这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战」。

一、Shuffle 机制

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

image.png

二、Partition 分区

image.png

image.png

三、Partition 分区实操案例

  1. 需求

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

    image.png

  2. 需求分析

    image.png

  3. 增加分区类

    public class ProvincePartitioner extends Partitioner<Text, FlowBean> {
    
        @Override
        public int getPartition(Text text, FlowBean flowBean, int numPartitions) {
            // text 是手机号
            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;
        }
    }
    
  4. 在驱动函数中增加自定义数据分区设置和ReduceTask设置

    job.setPartitionerClass(ProvincePartitioner.class);
    job.setNumReduceTasks(5);
    

四、WritableComparable 排序

1、排序概述

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

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

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

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

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

2、排序分类

2.1、部分排序

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

2.2、全排序

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

2.3、辅助排序

GroupingComparator分组

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

2.4、二次排序

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

3、自定义排序

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

五、WritableComparable 排序案例实操(全排序)

  1. 需求 根据手机号分区案例产生的结果,再次对总流量进行倒序排序。

    期望输出数据

    image.png

  2. 需求分析

    image.png

  3. 代码实现

    • FlowBean对象在在需求基础上增加了比较功能
    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 void write(DataOutput out) throws IOException {
    
            out.writeLong(upFlow);
            out.writeLong(downFlow);
            out.writeLong(sumFlow);
        }
    
        @Override
        public void readFields(DataInput in) throws IOException {
            this.upFlow = in.readLong();
            this.downFlow = in.readLong();
            this.sumFlow = in.readLong();
        }
    
        @Override
        public String toString() {
            return upFlow + "\t" + downFlow + "\t" + sumFlow;
        }
    
        @Override
        public int compareTo(FlowBean o) {
            // 总流量的倒序排序
            return Long.compare(this.sumFlow, o.sumFlow);
        }
    }
    
    • 编写Mapper类
    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, Context context) throws IOException, InterruptedException {
            // 获取一行
            String line = value.toString();
            // 切割
            String[] split = line.split("\t");
            // 封装
            outV.set(split[0]);
            outK.setUpFlow(Long.parseLong(split[1]));
            outK.setDownFlow(Long.parseLong(split[2]));
            outK.setSumFlow();
            // 写出
            context.write(outK, outV);
        }
    }
    
    • 编写Reducer类
    public class FlowReducer extends Reducer<FlowBean, Text, Text, FlowBean> {
    
        @Override
        protected void reduce(FlowBean key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
            for (Text value : values) {
                context.write(value, key);
            }
        }
    }
    
    • 编写Driver类
    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 设置jar
            job.setJarByClass(FlowDriver.class);
            // 3 关联mapper 和Reducer
            job.setMapperClass(FlowMapper.class);
            job.setReducerClass(FlowReducer.class);
            // 4 设置mapper 输出的key和value类型
            job.setMapOutputKeyClass(FlowBean.class);
            job.setMapOutputValueClass(Text.class);
            // 5 设置最终数据输出的key和value类型
            job.setOutputKeyClass(Text.class);
            job.setOutputValueClass(FlowBean.class);
            // 6 设置数据的输入路径和输出路径
            FileInputFormat.setInputPaths(job, new Path("D:\hadoop\output4"));
            FileOutputFormat.setOutputPath(job, new Path("D:\hadoop\output6"));
            // 7 提交job
            boolean result = job.waitForCompletion(true);
            System.exit(result ? 0 : 1);
        }
    }
    

六、WritableComparable 排序案例实操(区内排序)

  1. 需求

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

  2. 需求分析

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

    image.png

  3. 案例实操

    • 增加自定义分区类
    public class ProvincePartitioner2 extends Partitioner<FlowBean, Text> {
    
        @Override
        public int getPartition(FlowBean flowBean, Text text, int numPartitions) {
            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;
        }
    }
    
    • 在驱动类中添加分区类
    job.setPartitionerClass(ProvincePartitioner2.class);
    job.setNumReduceTasks(5);
    

七、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类型要对应起来。
  6. 自定义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);
    

八、Combiner 合并案例实操

  1. 需求

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

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

  2. 需求分析

    image.png

  3. 案例实操-方案一

    • 增加一个WordCountCombiner类继承Reducer
    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);
        }
    }
    
    • 在WordcountDriver驱动类中指定Combiner
    job.setCombinerClass(WordCountCombiner.class);
    
  4. 案例实操-方案二

    将WordcountReducer作为Combiner在WordcountDriver驱动类中指定

    // 指定需要使用Combiner,以及用哪个类作为Combiner的逻辑
    job.setCombinerClass(WordCountReducer.class);
    

    image.png

九、友情链接

大数据Hadoop-MapReduce学习之旅第二篇

大数据Hadoop-MapReduce学习之旅第一篇