MapReduce学习三:Shuffle

一、MapReduce详细工作流程

Map阶段

reduce阶段

二、Shuffle机制

流程图

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

2.1Partition分区

分区指的是:将MapReduce统计的结果按照条件输出到不同的文件中。

public int getPartition(K key,V value,int numReduceTasks){
    return (key.hashCode()&Intger.MAX_VALUE)%numReduceTasks;
}

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

自定义Partitioner步骤

  1. 自定义继承Partitioner,重写getPartition()方法
  2. 在Job驱动中,设置自定义Partitioner
  3. 自定义Partition后,要根据自定义Partitioner的逻辑设置相应数量的ReduceTask

分区总结:

  • 如果ReduceTask的数量>getPartition的结果数,则会多生产几个空的输出文件;
  • 如果1ReduceTask的数量
  • 如果ReduceTask的数量=1,无论MapTask端输出多少分区文件,最终都是ReduceTask,最终也只是产生一个结果文件;
  • 分区号必须从零开始,逐一累加;

2.1.1案例分析:

 需求分析

在之前Mapreduce学习文章中的统计流量的案例基础上添加代码

1、添加分区类

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.判断是哪个省
        if ("136".equals(preNum)) {
            partition = 0;
        } else if ("137".equals(preNum)) {
            partition = 1;
        } else if ("138".equals(preNum)) {
            partition = 2;
        } else if ("139".equals(preNum)) {
            partition = 3;
        }
        return partition;
    }
}

2、驱动类中增加分区设置和ReduceTask设置

//8.指定自定义数据分区和指定reduce task数量

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

2.2WritableCompare排序

2.2.1 概述

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

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

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

  • 对于MapTask,它会将处理的结果暂时放到环形缓冲区,当环形缓冲区使用率达到一定阈值后,再对缓冲区中的数据进行一次快速排序,并将这些有序数据溢写到磁盘上,而当数据处理完毕之后,它会对磁盘上所有文件进行归并排序。
  • 对于ReduceTask,它从每个MapTask上远程拷贝相应的数据文件,如果文件大小超过一定阈值,则溢写磁盘上,否则存储在内存中。如果磁盘上文件数目达到一定阈值,则进行一次归并排序以生成一个更大文件;如果内存中文件大小或者数目超过一定阈值,则进行一次合并后将数据溢写到磁盘上。当所有数据拷贝完毕后,ReduceTask统一对内存和磁盘上的所有数据进行一次归并排序。

2.2.2排序的分类

部分排序

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

全排序

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

辅助排序(分组排序)

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

二次排序

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

2.2.3自定义排序WritableComparable

原理分析

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

案例实操:WritableComparable排序案例实操(全排序)

 需求分析

代码实现

1.FlowBean基础上增加比较功能

public class FlowBean implements WritableComparable<FlowBean> {
    //1 实现Writable接口

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

    //2 反序列化时,需要反射调用空参构造函数

    public FlowBean() {

    }

    public FlowBean(long upFlow, long downFlow) {
        this.upFlow = upFlow;
        this.downFlow = downFlow;
        this.sumFlow = upFlow + downFlow;
    }

    //3.写序列化方法

    @Override
    public void write(DataOutput dataOutput) throws IOException {
        dataOutput.writeLong(upFlow);
        dataOutput.writeLong(downFlow);
        dataOutput.writeLong(sumFlow);
    }

    //4.写反序列化方法
    //5.顺序要和写序列化方法一致

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

    //6.toString方法

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

    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 set(long downFlow, long upFlow) {
        this.downFlow = downFlow;
        this.upFlow = upFlow;
        sumFlow = downFlow + upFlow;
    }

    //7.compare to 方法

    @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;
    }
}

2.编写mapper类

public class FlowCountSortMapper extends Mapper<LongWritable, Text,FlowBean,Text> {

    FlowBean bean = new FlowBean();
    Text v = new Text();


    @Override
    protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, FlowBean, Text>.Context context) throws IOException, InterruptedException {
        //1.获取一行
        String line = value.toString();

        //2.切割字段
        String[] fields = line.split("\t");

        //3.封装对象
        //取出手机号
        String phoneNum = fields[1];

        //取出上行流量和下行流量
        long upFlow = Long.parseLong(fields[fields.length - 3]);
        long downFlow = Long.parseLong(fields[fields.length - 2]);

        v.set(phoneNum);
        bean.set(downFlow, upFlow);

        //4.写出
        context.write(bean, v);
    }
}

3.Reducer

public class FlowCountSortReducer 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 text:values){
            context.write(text,key);
        }
    }
}

4.Driver

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

        args = new String[]{"d:/work/input1", "d:/work/output"};

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

        //2.设置jar加载路径
        job.setJarByClass(FlowCountDriver.class);

        //3.设置Map类和Reduce类
        job.setMapperClass(FlowCountSortMapper.class);
        job.setReducerClass(FlowCountSortReducer.class);

        //4.设置Map输出
        job.setMapOutputKeyClass(FlowBean.class);
        job.setMapOutputValueClass(Text.class);

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

        //8.指定自定义数据分区和指定reduce task数量
//        job.setPartitionerClass(ProvincePartitioner.class);
//        job.setNumReduceTasks(5);

        //6.设置输入和输出路径
        FileInputFormat.setInputPaths(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));

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

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

1.需求

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

2.需求分析

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

 只需要将之前分区代码注释放开就ok了

       //8.指定自定义数据分区和指定reduce task数量
        job.setPartitionerClass(ProvincePartitioner.class);
        job.setNumReduceTasks(5);

2.3Combiner合并

(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方法,然后在驱动类中配置。

案例实操:wordcount案例优化:

对每个MapTask的输出进行局部汇总;期望经过Combiner合并,减少输出。

Combiner类

public class WordCountCombiner extends Reducer<Text, IntWritable, Text, IntWritable> {

    IntWritable v = 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 value : values) {
            sum += value.get();
        }
        v.set(sum);
        context.write(key, v);
    }
}

driver类

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

        args = new String[]{"D:/WORK/input3",
                "D:/WORK/output"};

        //1.获取配置信息以及封装任务
        Configuration configuration = new Configuration();
        Job job = Job.getInstance(configuration);

        //2.设置jar加载路径
        job.setJarByClass(WordCountDriver.class);

        //3.设置map和reduce类
        job.setMapperClass(WordCountMapper.class);
        job.setReducerClass(WordCountReducer.class);

        //4.设置map输出
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);

        //5.设置最终输出kv类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        //设置合并规则
        job.setCombinerClass(WordCountCombiner.class);

        //6.设置输入和输出路径
        FileInputFormat.setInputPaths(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));


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

或者不用写新的Combiner类,因为内容和Reducer完全一样,可以在Driver类中直接指定Reducer为合并规则,这样效果也是相同的。

2.4GroupingComparator分组(辅助排序)

对Reduce阶段的数据根据某一个或几个字段进行分组。

分组排序步骤:

(1)自定义类继承WritableComparator

(2)重写compare()方法

(3)创建一个构造将比较对象的类传给父类

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

案例实操:

 需求分析:

 代码实现:

Bean

public class OrderBean implements WritableComparable<OrderBean> {

    //订单id号

    private int order_id;

    //价格

    private double price;

    public OrderBean() {
    }

    public OrderBean(int order_id, double price) {
        this.order_id = order_id;
        this.price = price;
    }

    /**
     * 二次排序
     *
     * @param o
     * @return
     */
    @Override
    public int compareTo(OrderBean o) {
        int result;

        if (order_id > o.getOrder_id()) {
            result = 1;
        } else if (order_id < o.getOrder_id()) {
            result = -1;
        } else {
            result = price > o.getPrice() ? -1 : 1;
        }
        return result;
    }

    @Override
    public void write(DataOutput dataOutput) throws IOException {
        dataOutput.writeInt(order_id);
        dataOutput.writeDouble(price);
    }

    @Override
    public void readFields(DataInput dataInput) throws IOException {
        order_id = dataInput.readInt();
        price = dataInput.readDouble();
    }

    @Override
    public String toString() {
        return order_id + "\t" + price;
    }

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

mapper

public class OrderSortMapper extends Mapper<LongWritable, Text, OrderBean, NullWritable> {

    OrderBean k = new OrderBean();

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

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

        //2截取
        String[] fields = line.split("\t");

        //3封装对象
        k.setOrder_id(Integer.parseInt(fields[0]));
        k.setPrice(Double.parseDouble(fields[2]));

        //4写出
        context.write(k, NullWritable.get());
    }
}

GroupingComparator

public class OrderSortGroupingComparator extends WritableComparator {

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

    @Override
    public int compare(WritableComparable a, WritableComparable b) {
        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;
    }
}

Reducer

public class OrderSortReducer extends Reducer<OrderBean, NullWritable, OrderBean, NullWritable> {
    @Override
    protected void reduce(OrderBean key, Iterable<NullWritable> values, Reducer<OrderBean, NullWritable, OrderBean, NullWritable>.Context context) throws IOException, InterruptedException {
        context.write(key, NullWritable.get());
    }
}

driver

public class OrderSortDriver {
    public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
        //输入输出路径需要根据自己电脑上实际的输入输出路径设置
        args = new String[]{"d:/WORK/input5",
                "d:/output1"};
        //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);
    }
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿星_Alex

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值