十二、MapReduce中的Shuffle

       Map方法之后,Reduce方法之前的数据处理过程称之为Shuffle,本文主要介绍Shuffle的相关内容。关注专栏《破茧成蝶——Hadoop篇》查看相关系列的文章~


目录

一、Shuffle过程

二、分区

2.1 分区介绍

2.2 分区案例

2.2.1 需求与数据

2.2.2 编写分区类

2.2.3 更改驱动类

2.2.4 测试

三、WritableComparable排序

3.1 排序概述

3.2 自定义WritableComparable排序实现全排序

3.2.1 需求与数据

3.2.2 编写Bean类,实现WritableComparable类

3.2.3 编写Mapper类

3.2.4 编写Reducer类

3.2.5 编写Driver类

3.2.6 测试

3.3 自定义WritableComparable排序实现区内排序

四、Combiner合并

4.1 Combiner简介

4.2 Combiner示例

4.2.1 需求与数据

4.2.2 测试

五、GroupingComparator分组

5.1 需求

5.2 编写Bean类

5.3 编写Mapper类

5.4 编写Comparator

5.5 编写Reducer

5.6 编写Driver

5.7 测试


一、Shuffle过程

       具体Shuffle过程详解:(1)MapTask收集我们的map()方法输出的kv对,放到内存缓冲区中。(2)从内存缓冲区不断溢出本地磁盘文件,可能会溢出多个文件。(3)多个溢出文件会被合并成大的溢出文件。(4)在溢出过程及合并的过程中,都要调用Partitioner进行分区和针对key进行排序。(5)ReduceTask根据自己的分区号,去各个MapTask机器上取相应的结果分区数据。(6)ReduceTask会取到同一个分区的来自不同MapTask的结果文件,ReduceTask会将这些文件再进行合并(归并排序)。(7)合并成大文件后,Shuffle的过程也就结束了,后面进入ReduceTask的逻辑运算过程(从文件中取出一个一个的键值对Group,调用用户自定义的reduce()方法)。

       Shuffle中的缓冲区大小会影响到MapReduce程序的执行效率,原则上说,缓冲区越大,磁盘io的次数越少,执行速度就越快。缓冲区的大小可以通过参数调整,参数:io.sort.mb默认100M。

二、分区

2.1 分区介绍

       可以使用分区实现将数据的不同统计结果输出到不同的分区中。这里需要注意的是:(1)如果ReduceTask的数量大于分区的数量,则会产生几个空的输出文件,即part-r-000xx;(2)如果ReduceTask的数量小于分区的数量(ReduceTask数量大于一),则会有一部分分区数据无处输出,此时会报异常;(3)如果ReduceTask的数量为一,则不管MapTask端输出多少个分区文件,则最终结果都交给这一个ReduceTask,最终也会形成一个part-r-00000文件;(4)分区号必须从零开始,逐一累加。

2.2 分区案例

2.2.1 需求与数据

       数据为《十、Hadoop的序列化》中例子的数据,即Nginx的日志数据,需求为:ip地址前两位为10、60、22、11分别放在不同的分区中,其他的放在另外一个分区中。

2.2.2 编写分区类

       此时的分区只需要在《十、Hadoop的序列化》中的例子中增加一个分区类即可,如下所示:

package com.xzw.hadoop.mapreduce.nginx;

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

/**
 * @author: xzw
 * @create_date: 2020/8/8 14:26
 * @desc: ip地址前两位10/60/22/11分别放在一个分区中,其他的放在另外一个分区中
 * @modifier:
 * @modified_date:
 * @desc:
 */
public class NginxPartitioner extends Partitioner<Text, NginxBean> {

    public int getPartition(Text key, NginxBean value, int numPartitions) {
        //1、获取ip地址前两位
        String ip = key.toString().substring(0, 2);

        //2、判断,进行分区
        if ("10".equals(ip)) {
            return 0;
        } else if ("60".equals(ip)) {
            return 1;
        } else if ("22".equals(ip)) {
            return 2;
        } else if ("11".equals(ip)) {
            return 3;
        }
        return 4;

    }
}

2.2.3 更改驱动类

       在之前例子的Driver驱动类中添加如下内容:

//添加分区相关设置
job.setPartitionerClass(NginxPartitioner.class);
//指定响应数量的reduce task
job.setNumReduceTasks(5);

2.2.4 测试

       首先先来看一下未加分区时的结果,期望输出一个part-r-00000。

       接下来,是添加分区之后的结果:

三、WritableComparable排序

3.1 排序概述

       排序是MapReduce框架中最重要的操作之一。MapTask和ReduceTask均会对数据按照Key进行排序。该操作属于Hadoop的默认行为。任何应用程序中的数据均会被排序,而不管逻辑上是否需要。默认排序是按照字典顺序排序且实现该排序的方法是快速排序。

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

3.2 自定义WritableComparable排序实现全排序

3.2.1 需求与数据

       需求:根据《十、Hadoop的序列化》中的例子的结果对总size进行降序排序:

3.2.2 编写Bean类,实现WritableComparable类

package com.xzw.hadoop.mapreduce.writablecomparable;

import org.apache.hadoop.io.Writable;
import org.apache.hadoop.io.WritableComparable;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

/**
 * @author: xzw
 * @create_date: 2020/7/28 10:01
 * @desc:
 * @modifier:
 * @modified_date:
 * @desc:
 */
public class NginxBean implements WritableComparable<NginxBean> {
    private long size;//size

    //反序列化时,需要反射调用空参构造器,所以必须有空参构造器
    public NginxBean() {
    }

    public void set(long size) {
        this.size = size;
    }

    public long getSize() {
        return size;
    }

    public void setSize(long size) {
        this.size = size;
    }

    /**
     * 序列化方法
     * @param dataOutput
     * @throws IOException
     */
    public void write(DataOutput dataOutput) throws IOException {
        dataOutput.writeLong(size);
    }

    /**
     * 反序列化方法:反序列化方法读取顺序必须跟序列化方法写顺序一致
     * @param dataInput
     * @throws IOException
     */
    public void readFields(DataInput dataInput) throws IOException {
        this.size = dataInput.readLong();
    }

    /**
     * 编写toString方法,方便后续打印到文本
     * @return
     */
    @Override
    public String toString() {
        return size + "\t";
    }

    public int compareTo(NginxBean o) {
        return Long.compare(o.size, this.size);
    }
}

3.2.3 编写Mapper类

package com.xzw.hadoop.mapreduce.writablecomparable;

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

import java.io.IOException;

/**
 * @author: xzw
 * @create_date: 2020/8/8 15:18
 * @desc:
 * @modifier:
 * @modified_date:
 * @desc:
 */
public class NginxSortMapper extends Mapper<LongWritable, Text, NginxBean, Text> {
    NginxBean k = new NginxBean();
    Text v = new Text();

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

        String line = value.toString();

        String[] fields = line.split("\t");

        String ip = fields[0];
        long size = Long.parseLong(fields[1]);

        k.set(size);
        v.set(ip);

        context.write(k, v);
    }
}

3.2.4 编写Reducer类

package com.xzw.hadoop.mapreduce.writablecomparable;

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

import java.io.IOException;

/**
 * @author: xzw
 * @create_date: 2020/8/8 15:19
 * @desc:
 * @modifier:
 * @modified_date:
 * @desc:
 */
public class NginxSortReducer extends Reducer<NginxBean, Text, Text, NginxBean> {
    @Override
    protected void reduce(NginxBean key, Iterable<Text> values, Context context) throws IOException,
            InterruptedException {
        for (Text value: values) {
            context.write(value, key);
        }
    }
}

3.2.5 编写Driver类

package com.xzw.hadoop.mapreduce.writablecomparable;

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;

/**
 * @author: xzw
 * @create_date: 2020/8/8 15:19
 * @desc:
 * @modifier:
 * @modified_date:
 * @desc:
 */
public class NginxSortDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        args = new String[]{"e:/output", "e:/output1"};

        Job job = Job.getInstance(new Configuration());

        job.setJarByClass(NginxSortDriver.class);

        job.setMapperClass(NginxSortMapper.class);
        job.setReducerClass(NginxSortReducer.class);

        job.setMapOutputKeyClass(NginxBean.class);
        job.setMapOutputValueClass(Text.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(NginxBean.class);

        FileInputFormat.setInputPaths(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));

        boolean b = job.waitForCompletion(true);
        System.exit(b ? 0 : 1);
    }
}

3.2.6 测试

3.3 自定义WritableComparable排序实现区内排序

       基于3.2,此时只需要添加Partitioner类即可,如下所示:

package com.xzw.hadoop.mapreduce.writablecomparable;

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

/**
 * @author: xzw
 * @create_date: 2020/8/8 14:26
 * @desc: ip地址前两位10/60/22/11分别放在一个分区中,其他的放在另外一个分区中
 * @modifier:
 * @modified_date:
 * @desc:
 */
public class NginxSortPartitioner extends Partitioner<NginxBean, Text> {

    @Override
    public int getPartition(NginxBean key, Text value, int numPartitions) {
        //1、获取ip地址前两位
        String ip = value.toString().substring(0, 2);

        //2、判断,进行分区
        if ("10".equals(ip)) {
            return 0;
        } else if ("60".equals(ip)) {
            return 1;
        } else if ("22".equals(ip)) {
            return 2;
        } else if ("11".equals(ip)) {
            return 3;
        }
        return 4;
    }
}

       在Driver类中添加如下内容:

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

       再次测试,得到结果如下所示:

四、Combiner合并

4.1 Combiner简介

       Combiner是MapReduce程序中除了Mapper和Reducer之外的一个组件。它的父类就是Reducer,它跟Reducer的区别在于运行的位置。Combiner是在每个MapTask所在的节点运行,Reducer是接收全局所有Mapper的输出结果。Combiner的意义就是对每个MapTask的输出进行局部汇总,以减小网络传输量。Combiner能够应用的前提是不能影响最后的业务逻辑,而且,Combiner的输出KV应该跟Reducer的输入KV类型对应起来。

4.2 Combiner示例

4.2.1 需求与数据

       实现数据的WordCount,最终目的为减少网络的传输量,数据如下(相关代码可以参考《九、Hadoop核心组件之MapReduce》中WordCount的例子):

4.2.2 测试

       先运行一下未设置Combiner时的程序,查看网络传输量如下:

       在Driver类中添加如下代码,再次测试:

job.setCombinerClass(WcReducer.class);

五、GroupingComparator分组

       此分组的意义在于对Reducer阶段的数据根据某一个或几个字段进行分组。下面通过一个例子来进行叙述吧。

5.1 需求

       现有如下数据,三列分别为:订单id,商品id和成交金额。

       现有需要求出每个订单中最贵的商品。

       分析:每个订单分组,降序输出,然后取第一位数据即可。

5.2 编写Bean类

package com.xzw.hadoop.mapreduce.groupingcomparator;

import org.apache.hadoop.io.WritableComparable;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

/**
 * @author: xzw
 * @create_date: 2020/8/8 16:41
 * @desc:
 * @modifier:
 * @modified_date:
 * @desc:
 */
public class OrderBean implements WritableComparable<OrderBean> {

    private String orderId;
    private String productId;
    private double price;

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

    public String getOrderId() {
        return orderId;
    }

    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }

    public String getProductId() {
        return productId;
    }

    public void setProductId(String productId) {
        this.productId = productId;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public int compareTo(OrderBean o) {//二次排序
        int compare = this.orderId.compareTo(o.orderId);

        if (compare == 0) {
            return Double.compare(o.price, this.price);
        } else {
            return compare;
        }
    }

    @Override
    public void write(DataOutput out) throws IOException {
        out.writeUTF(orderId);
        out.writeUTF(productId);
        out.writeDouble(price);
    }

    @Override
    public void readFields(DataInput in) throws IOException {
        this.orderId = in.readUTF();
        this.productId = in.readUTF();
        this.price = in.readDouble();
    }
}

5.3 编写Mapper类

package com.xzw.hadoop.mapreduce.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;

/**
 * @author: xzw
 * @create_date: 2020/8/8 16:47
 * @desc:
 * @modifier:
 * @modified_date:
 * @desc:
 */
public class OrderMapper extends Mapper<LongWritable, Text, OrderBean, NullWritable> {

    private OrderBean orderBean = new OrderBean();

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String[] fields = value.toString().split("\t");

        orderBean.setOrderId(fields[0]);
        orderBean.setProductId(fields[1]);
        orderBean.setPrice(Double.parseDouble(fields[2]));

        context.write(orderBean, NullWritable.get());
    }
}

5.4 编写Comparator

package com.xzw.hadoop.mapreduce.groupingcomparator;

import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;

/**
 * @author: xzw
 * @create_date: 2020/8/8 16:48
 * @desc:
 * @modifier:
 * @modified_date:
 * @desc:
 */
public class OrderComparator extends WritableComparator {

    public OrderComparator() {
        super(OrderBean.class, true);
    }

    @Override
    public int compare(WritableComparable a, WritableComparable b) {
        OrderBean oa = (OrderBean) a;
        OrderBean ob = (OrderBean) b;

        return oa.getOrderId().compareTo(ob.getOrderId());
    }
}

5.5 编写Reducer

package com.xzw.hadoop.mapreduce.groupingcomparator;

import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

/**
 * @author: xzw
 * @create_date: 2020/8/8 16:47
 * @desc:
 * @modifier:
 * @modified_date:
 * @desc:
 */
public class OrderReducer 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());
    }
}

5.6 编写Driver

package com.xzw.hadoop.mapreduce.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;

/**
 * @author: xzw
 * @create_date: 2020/8/8 16:47
 * @desc:
 * @modifier:
 * @modified_date:
 * @desc:
 */
public class OrderDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        //设置输入输出路径(便于测试)
        args = new String[]{"e:/input/orders.txt", "e:/output"};

        Job job = Job.getInstance(new Configuration());

        job.setJarByClass(OrderDriver.class);

        job.setMapperClass(OrderMapper.class);
        job.setReducerClass(OrderReducer.class);

        job.setMapOutputKeyClass(OrderBean.class);
        job.setMapOutputValueClass(NullWritable.class);

        job.setGroupingComparatorClass(OrderComparator.class);

        job.setOutputKeyClass(OrderBean.class);
        job.setOutputValueClass(NullWritable.class);

        FileInputFormat.setInputPaths(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));

        boolean b = job.waitForCompletion(true);
        System.exit(b ? 0 : 1);
    }
}

5.7 测试

       至此,本文就讲解完了,你们在这个过程中遇到了什么问题,欢迎留言,让我看看你们遇到了什么问题~

猜你喜欢

转载自blog.csdn.net/gdkyxy2013/article/details/107830351