【Hadoop学习之MapReduce】_20MR之分区和排序

一、Shuffle机制

在这里插入图片描述
Map方法之后,Reduce方法之前的数据处理过程称之为Shuffle(多称为洗牌)

二、Partition分区

(一)Partition分区详解

  1. 默认Partition分区为HashPartitioner

    public class HashPartitioner<K, V> extends Partitioner<K, V> {
    
      public int getPartition(K key, V value, int numReduceTasks) {
        return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
      }
    
    }
    

    **注意:**默认分区是根据keyhashCodeReduceTasks个数取模得到的。用户没法控制哪个key存储到哪个分区

  2. 自定义Partition分区步骤

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

    public class CustomPartitioner extends Partitioner<Text, FlowBean> {
     	@Override
    	public int getPartition(Text key, FlowBean value, int numPartitions) {
              // 控制分区代码逻辑
        … …
    		return partition;
    	}
    }
    

    (2)在Job驱动中,设置自定义Partitioner

    job.setPartitionerClass(CustomPartitioner.class);
    

    (3)自定义Partition后,要根据自定义Partitioner的逻辑设置相应数量的ReduceTask

    job.setNumReduceTasks(5);
    
  3. 分区总结

    (1)如果ReduceTask的数量> getPartition的结果数,则会多产生几个空的输出文件part-r-000xx;

    (2)如果1<ReduceTask的数量<getPartition的结果数,则有一部分分区数据无处安放,会Exception;

    (3)如果ReduceTask的数量=1,则不管MapTask端输出多少个分区文件,最终结果都交给这一个ReduceTask,最终也就只会产生一个结果文件 part-r-00000;

    (4)分区号必须从零开始,逐一累加。

  4. 示例分析

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

    (1)job.setNumReduceTasks(1); 会正常运行,只不过会产生一个输出文件

    (2)job.setNumReduceTasks(2); 会报错

    (3)job.setNumReduceTasks(6); 大于5,程序会正常运行,会产生空文件

(二)Partition分区案例

  1. 需求

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

  2. 输入数据

    电话号码。

  3. 期望输出

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

  4. 在序列化案例的基础上进行如下操作

    (1)创建ProvincePartitioner分区类:

    package com.easysir.flowsum;
    
    import org.apache.hadoop.io.Text;
    import org.apache.hadoop.mapreduce.Partitioner;
    
    public class ProvincePartitioner extends Partitioner<Text, FlowBean> {
    
        @Override
        public int getPartition(Text key, FlowBean value, int numPartitions) {
    
            // 1 获取电话号码的前三位
            String preNum = key.toString().substring(0, 3);
    
            int partition = 4;
    
            // 2 判断是哪个省
            switch (preNum) {
                case "136":
                    partition = 0;
                    break;
                case "137":
                    partition = 1;
                    break;
                case "138":
                    partition = 2;
                    break;
                case "139":
                    partition = 3;
                    break;
            }
    
            return partition;
        }
    }
    

    (2)在驱动类FlowsumDriver中增加自定义数据分区设置和ReduceTask设置:

    // 指定自定义数据分区
    job.setPartitionerClass(ProvincePartitioner.class);
    // 同时指定相应数量的reduce task
    job.setNumReduceTasks(5);
    

三、WritableComparable排序

(一)WritableComparable排序详解

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

  1. 排序概述

    MapTaskReduceTask均会对数据按照key进行排序,该操作属于Hadoop的默认行为。任何应用程序中的数据均会被排序,而不管逻辑上是否需要。默认排序是按照字典顺序排序,且实现该排序的方法是快速排序

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

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

  2. 排序分类

    (1)部分排序

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

    (2)全排序

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

    (3)辅助排序(GroupingComparator分组)

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

    (4)二次排序

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

  3. 自定义排序WritableComparable

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

(二)WritableComparable排序案例之全排序

  1. 需求

    在序列化案例的基础上,将结果按照总流量降序排列。

  2. 创建包名:com.easysir.sort

  3. 创建FlowBean类,实现WritableComparable接口,重写compareTo方法:

    package com.easysir.sort;
    
    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() {
            super();
        }
    
        public FlowBean(long upFlow, long downFlow) {
            super();
            this.upFlow = upFlow;
            this.downFlow = downFlow;
            this.sumFlow = upFlow + downFlow;
        }
    
        // 比较方法
        @Override
        public int compareTo(FlowBean bean) {
            int result;
    
            // 核心比较方法
            if (sumFlow > bean.getSumFlow()){
                result = -1;
            }else if (sumFlow < bean.getSumFlow()) {
                result = 1;
            }else {
                result = 0;
            }
    
            return result;
        }
    
        // 序列化方法
        @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 {
            upFlow = in.readLong();
            downFlow = in.readLong();
            sumFlow = in.readLong();
        }
    
        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;
        }
    
        @Override
        public String toString() {
            return upFlow + "\t" + downFlow + "\t" + sumFlow;
        }
    }
    
  4. 创建FlowCountSortMapper类:

    package com.easysir.sort;
    
    import org.apache.hadoop.io.LongWritable;
    import org.apache.hadoop.io.Text;
    import org.apache.hadoop.mapreduce.Mapper;
    
    import java.io.IOException;
    
    /**
     * description
     *
     * @author Hu.Wang 2020/02/10 13:23
     */
    public class FlowCountSortMapper extends Mapper<LongWritable, Text, FlowBean, Text> {
    
        FlowBean k = new FlowBean();
        Text v = new Text();
    
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
    
            // 1 获取一行
            String line = value.toString();
    
            // 2 按\t切割
            String[] fields = line.split("\t");
    
            // 3 封装对象
            String phoneNum = fields[0];
    
            long upFlow = Long.parseLong(fields[1]);
            long downFlow = Long.parseLong(fields[2]);
            long sumFlow = Long.parseLong(fields[3]);
    
            k.setUpFlow(upFlow);
            k.setDownFlow(downFlow);
            k.setSumFlow(sumFlow);
    
            v.set(phoneNum);
    
            // 4 写出
            context.write(k, v);
        }
    
    }
    
  5. 创建FlowCountSortReducer类:

    package com.easysir.sort;
    
    import org.apache.hadoop.io.Text;
    import org.apache.hadoop.mapreduce.Reducer;
    
    import java.io.IOException;
    
    public class FlowCountSortReducer 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);
            }
            
        }
    }
    
  6. 创建FlowCountSortDriver类:

    package com.easysir.sort;
    
    import com.easysir.flowsum.FlowsumDriver;
    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 FlowCountSortDriver {
    
        public static void main(String[] args)
                throws IOException, ClassNotFoundException, InterruptedException {
    
            // 输入输出路径需要根据自己电脑上实际的输入输出路径设置
            args = new String[] { "E:\\idea-workspace\\mrWordCount\\output", "E:\\idea-workspace\\mrWordCount\\output1" };
    
            // 1 获取配置信息
            Configuration conf = new Configuration();
            Job job = Job.getInstance(conf);
    
            // 2 指定jar包所在本地路径
            job.setJarByClass(FlowsumDriver.class);
    
            // 3 关联mapper和reducer
            job.setMapperClass(FlowCountSortMapper.class);
            job.setReducerClass(FlowCountSortReducer.class);
    
            // 4 指定map输出kv类型
            job.setMapOutputKeyClass(FlowBean.class);
            job.setMapOutputValueClass(Text.class);
    
            // 5 指定最终输出类型
            job.setOutputKeyClass(Text.class);
            job.setOutputValueClass(FlowBean.class);
    
            // 6 指定输入输出路径
            FileInputFormat.setInputPaths(job, new Path(args[0]));
            FileOutputFormat.setOutputPath(job, new Path(args[1]));
    
            // 7 提交job
            job.waitForCompletion(true);
        }
    }
    

(三)WritableComparable排序案例之区内排序

  1. 需求

    在全排序案例的基础上将手机号分区。

  2. 在全排序案例的基础上增加自定义分区类ProvincePartitioner

    package com.easysir.sort;
    
    import org.apache.hadoop.io.Text;
    import org.apache.hadoop.mapreduce.Partitioner;
    
    /**
     * description
     *
     * @author Hu.Wang 2020/02/10 14:11
     */
    public class ProvincePartitioner extends Partitioner<FlowBean, Text> {
    
        @Override
        public int getPartition(FlowBean bean, Text text, int numPartitions) {
    
            // 1 获取手机号前三位
            String preNum = text.toString().substring(0, 3);
    
            int partition = 4;
    
            // 2 根据手机号前三位判断分区
            switch (preNum) {
                case "136":
                    partition = 0;
                    break;
                case "137":
                    partition = 1;
                    break;
                case "138":
                    partition = 2;
                    break;
                case "139":
                    partition = 3;
                    break;
            }
    
            return partition;
        }
    }
    
  3. 在驱动类FlowCountSortDriver中添加分区类配置:

    // 加载自定义分区类
    job.setPartitionerClass(ProvincePartitioner.class);
    // 设置Reducetask个数
    job.setNumReduceTasks(5);
    

四、Combiner合并

(一)Combiner合并详解

  1. CombinerMR程序中MapperReducer之外的一种组件。

  2. Combiner组件的父类就是Reducer

  3. CombinerReducer的区别在于运行的位置

    (1)Combiner是在每一个MapTask所在的节点运行;

    (2)Reducer是接收全局所有Mapper的输出结果。

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

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

  6. 自定义Combiner实现步骤:

    (1)自定义一个Combiner继承Reducer,重写Reduce方法

    public class WordcountCombiner extends Reducer<Text, IntWritable, Text,IntWritable>{
    
    	@Override
    	protected void reduce(Text key, Iterable<IntWritable> values,Context context) throws IOException, InterruptedException {
    
            // 1 汇总操作
    		int count = 0;
    		for(IntWritable v :values){
    			count += v.get();
    		}
    
            // 2 写出
    		context.write(key, new IntWritable(count));
    	}
    }
    

    (2)在驱动类中设置:

    job.setCombinerClass(WordcountCombiner.class);
    

(二)Combiner合并案例

  1. 需求

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

  2. 输入数据

    banzhang ni hao
    xihuan hadoop banzhang
    banzhang ni hao
    xihuan hadoop banzhang
    
  3. 期望输出

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

    Combine input records=12
    Combine output records=5
    
  4. 在WordCount案例基础上进行如下操作

    方案一:

    ​ (1)创建WordcountCombiner类继承Reducer

    package com.easysir.wordcount;
    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<Text, IntWritable, Text, IntWritable>{
    
        IntWritable v = new IntWritable();
    
        @Override
        protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
    
            // 1 汇总
            int sum = 0;
    
            for(IntWritable value :values){
                sum += value.get();
            }
    
            v.set(sum);
    
            // 2 写出
            context.write(key, v);
        }
    }
    

    ​ (2)在WordcountDriver驱动类中指定Combiner

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

    方案二:

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

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

五、GroupingComparator分组(辅助)排序

(一)GroupingComparator详解

GroupingComparator分组(辅助)排序:对Reduce阶段的数据根据一个或几个字段进行分组

分组排序的步骤:

  1. 自定义类继承WritableComparator

  2. 重写compare()方法

    @Override
    public int compare(WritableComparable a, WritableComparable b) {
        // 比较的业务逻辑
        return result;
    }
    
  3. 创建一个构造将比较对象的类传给父类

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

(二)GroupingComparator分组排序案例

  1. 需求

    输出每一个订单中最贵的商品。

  2. 输入数据

    0000001	Pdt_01	222.8
    0000002	Pdt_05	722.4
    0000001	Pdt_02	33.8
    0000003	Pdt_06	232.8
    0000003	Pdt_02	33.8
    0000002	Pdt_03	522.8
    0000002	Pdt_04	122.4
    
  3. 期望输出数据

    1	222.8
    2	722.4
    3	232.8
    
  4. 创建包:com.easysir.groupingcomparator

  5. 创建订单信息类OrderBean

    package com.easysir.groupingcomparator;
    
    import org.apache.hadoop.io.WritableComparable;
    
    import java.io.DataInput;
    import java.io.DataOutput;
    import java.io.IOException;
    
    public class OrderBean implements WritableComparable<OrderBean> {
    
        private int order_id;   // 订单id
        private double price;   // 价格
    
        public OrderBean() {
            super();
        }
    
        public OrderBean(int order_id, double price) {
            super();
            this.order_id = order_id;
            this.price = price;
        }
    
        @Override
        public int compareTo(OrderBean bean) {
            // 先按照订单id升序排序,若订单相同则按价格降序排序
            int result;
    
            if (order_id > bean.getOrder_id()) {
                result = 1;
            }else if (order_id < bean.getOrder_id()) {
                result = -1;
            }else {
                if (price > bean.getPrice()){
                    result = -1;
                }else if (price < bean.getPrice()){
                    result = 1;
                }else {
                    result = 0;
                }
            }
    
            return 0;
        }
    
        @Override
        public void write(DataOutput out) throws IOException {
            out.writeInt(order_id);
            out.writeDouble(price);
        }
    
        @Override
        public void readFields(DataInput in) throws IOException {
            order_id = in.readInt();
            price = in.readDouble();
        }
    
        public int getOrder_id() {
            return order_id;
        }
    
        public void setOrder_id(int order_id) {
            this.order_id = order_id;
        }
    
        public double getPrice() {
            return price;
        }
    
        public void setPrice(double price) {
            this.price = price;
        }
    
        @Override
        public String toString() {
            return order_id + "\t" + price;
        }
    }
    
  6. 创建OrderSortMapper类:

    package com.easysir.groupingcomparator;
    
    import org.apache.hadoop.io.LongWritable;
    import org.apache.hadoop.io.NullWritable;
    import org.apache.hadoop.io.Text;
    import org.apache.hadoop.mapreduce.Mapper;
    
    import java.io.IOException;
    
    public class OrderSortMapper extends Mapper<LongWritable, Text, OrderBean, NullWritable> {
    
        OrderBean k = new OrderBean();
    
        @Override
        protected void map(LongWritable key, Text value, Context context)
                throws IOException, InterruptedException {
    
            // 1 获取一行
            String line = value.toString();
    
            // 2 切割
            String[] fields = line.split(" ");
    
            // 3 将订单id和价格封装至bean对象
            k.setOrder_id(Integer.parseInt(fields[0]));
            k.setPrice(Double.parseDouble(fields[1]));
    
            // 4 写出
            context.write(k, NullWritable.get());
        }
    }
    
  7. 创建OrderSortGroupingComparator

    package com.easysir.groupingcomparator;
    
    import org.apache.hadoop.io.WritableComparable;
    import org.apache.hadoop.io.WritableComparator;
    
    public class OrderSortGroupingComparator extends WritableComparator {
    
        public OrderSortGroupingComparator() {
            // 注意这里要传入两个参数,第一个参数为比较类,第二个参数若置false则将所有key置空,会报空指针异常
            super(OrderBean.class, true);
        }
    
        @Override
        public int compare(WritableComparable a, WritableComparable b) {
            // 只要id相同,则认定为相同的key
    
            OrderBean aBean = (OrderBean) a;
            OrderBean bBean = (OrderBean) b;
    
            int result;
            if (aBean.getOrder_id() > bBean.getOrder_id()){
                result = 1;
            }else if (aBean.getOrder_id() < bBean.getOrder_id()){
                result = -1;
            }else {
                result = 0;
            }
    
    
            return result;
        }
    }
    
  8. 创建OrderSortReducer类:

    package com.easysir.groupingcomparator;
    
    import org.apache.hadoop.io.NullWritable;
    import org.apache.hadoop.mapreduce.Reducer;
    
    import java.io.IOException;
    
    public class OrderSortReducer extends Reducer<OrderBean, NullWritable, OrderBean, NullWritable> {
    
        @Override
        protected void reduce(OrderBean key, Iterable<NullWritable> values, Context context)
                throws IOException, InterruptedException {
            context.write(key, NullWritable.get());
        }
    }
    
  9. 创建OrderSortDriver类:

    package com.easysir.groupingcomparator;
    
    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.fs.Path;
    import org.apache.hadoop.io.NullWritable;
    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 OrderSortDriver {
    
        public static void main(String[] args) throws Exception, IOException {
    
            // 输入输出路径需要根据自己电脑上实际的输入输出路径设置
            args = new String[] { "E:\\idea-workspace\\mrWordCount\\input\\grouping_data.txt", "E:\\idea-workspace\\mrWordCount\\output" };
    
            // 1 获取配置信息
            Configuration conf = new Configuration();
            Job job = Job.getInstance(conf);
    
            // 2 设置jar包加载路径
            job.setJarByClass(OrderSortDriver.class);
    
            // 3 加载map/reduce类
            job.setMapperClass(OrderSortMapper.class);
            job.setReducerClass(OrderSortReducer.class);
    
            // 4 设置map输出数据key和value类型
            job.setMapOutputKeyClass(OrderBean.class);
            job.setMapOutputValueClass(NullWritable.class);
    
            // 5 设置最终输出数据的key和value类型
            job.setOutputKeyClass(OrderBean.class);
            job.setOutputValueClass(NullWritable.class);
    
            // 6 设置输入数据和输出数据路径
            FileInputFormat.setInputPaths(job, new Path(args[0]));
            FileOutputFormat.setOutputPath(job, new Path(args[1]));
    
            // 8 设置reduce端的分组
            job.setGroupingComparatorClass(OrderSortGroupingComparator.class);
    
            // 7 提交
            boolean result = job.waitForCompletion(true);
            System.exit(result ? 0 : 1);
        }
    }
    
发布了31 篇原创文章 · 获赞 32 · 访问量 1296

猜你喜欢

转载自blog.csdn.net/qq_40947493/article/details/104252117
今日推荐