十六 Shuffle机制之WritableComparable排序 和 Combiner合并

博客详细介绍了MapReduce的排序机制,包括部分排序、全排序、辅助排序(GroupingComparator)以及Combiner合并。文章强调了排序的重要性,MapTask和ReduceTask如何对数据进行排序,以及如何通过自定义GroupingComparator进行分组。此外,还阐述了Combiner的作用,即进行局部汇总以减少网络传输量。

1 排序概述

  • 排序是MapReduce框架中最重要的操作之一.
  • MapTask和ReduceTask均会对数据按照key进行排序。该操作属于Hadoop的默认行为。任何应用程序中的数据均会被排序,而不管逻辑上是否需要。
  • 默认排序是按照字典顺序排序,且实现该排序的方法是快速排序。
  • 对于MapTask,它会将处理的结果暂时放到环形缓冲区中,当环形缓冲区使用率达到一定阈值后,再对缓冲区中的数据进行一次快速排序,并将这些有序数据溢写到磁盘上,而当数据处理完毕后,它会对磁盘上所有文件进行归并排序。
  • 对于ReduceTask,它从每个MapTask上远程拷贝相应的数据文件,如果文件大小超过一定阈值,则溢写磁盘上,否则存储在内存中。如果磁盘上文件数目达到一定阈值,则进行一次归并排序以生成一个更大文件;如果内存中文件大小或者数目超过一定阈值,则进行一次合并后将数据溢写到磁盘上。当所有数据拷贝完毕后,ReduceTask统一对内存和磁盘上的所有数据进行一次归并排序。

2 排序分类

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

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

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

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

3 部分排序

https://blog.youkuaiyun.com/andyonlines/article/details/104531313中的分区案例就是部分排序,它是每个分区内部有序.分区排序的实现自定义的数据类要实现WritableComparable接口,并在compareTo函数中定义自己的排序规则.

4 全排序

https://blog.youkuaiyun.com/andyonlines/article/details/104531313中的分区案例的driver类中去掉下面两行:

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

使程序只生产一个分区和一个文件文件.

5 GroupingComparator分组(辅助排序)

对Reduce阶段的数据根据某一个或几个字段进行分组。
分组排序步骤:
(1)自定义类继承WritableComparator

(2)重写compare()方法

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

5.1 GroupingComparator分组 的实例

1.需求

订单id商品id成交金额
0000001Pdt_01222.8
Pdt_0233.8
0000002Pdt_03522.8
Pdt_04122.4
Pdt_05722.4
0000003Pdt_06232.8
Pdt_0233.8

需要求出每一个订单中最贵的商品.

2.需求分析
(1)利用“订单id和成交金额”作为key,可以将Map阶段读取到的所有订单数据按照id升序排序,如果id相同再按照金额降序排序,发送到Reduce。
(2)在Reduce端利用groupingComparator将订单id相同的kv聚合成组,然后取第一个即是该订单中最贵商品,

  1. 代码
    GroupBean 类
package andy.group;

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

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

public class GroupBean implements WritableComparable<GroupBean> {
    private int group_id;
    private String pdt_id;
    private double price;

    public GroupBean() {
        super();
    }


    public GroupBean(int group_id, String pdt_id, double price) {
        set(group_id, pdt_id,price);
    }

    @Override
    public int compareTo(GroupBean o) {
        int result = 0;
        if (this.group_id > o.getGroup_id()){
            result = -1;
        }else if(this.group_id < o.getGroup_id()){
            result = 1;
        }else{
            result = this.price > o.getGroup_id() ? -1 : 1;
        }
        return result;
    }

    @Override
    public void write(DataOutput out) throws IOException {
        out.writeInt(group_id);
        out.writeUTF(pdt_id);
        out.writeDouble(price);
    }

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

    public int getGroup_id() {
        return group_id;
    }

    public void setGroup_id(int group_id) {
        this.group_id = group_id;
    }

    public String getPdt_id() {
        return pdt_id;
    }

    public void setPdt_id(String pdt_id) {
        this.pdt_id = pdt_id;
    }

    public double getPrice() {
        return price;
    }

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

    public void set(int group_id, String pdt_id, double price){
        this.group_id = group_id;
        this.pdt_id = pdt_id;
        this.price = price;
    }

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

GpMapper 类

package andy.group;

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 GpMapper extends Mapper<LongWritable, Text,GroupBean, NullWritable> {
    GroupBean k = new GroupBean();
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String [] line = value.toString().split("\t");
        //System.out.println(line[0] + "--" + line[2]);
        k.set(Integer.parseInt(line[0]),line[1],Double.parseDouble(line[2]));
        //k.set(1,line[1],0.0);
        context.write(k,NullWritable.get());
    }
}

GpGoup 类,作为分组器

package andy.group;

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

public class GpGoup extends WritableComparator {
    public GpGoup() {
        super(GroupBean.class,true);
    }

    @Override
    public int compare(WritableComparable a, WritableComparable b) {
        GroupBean g1 = (GroupBean) a;
        GroupBean g2 = (GroupBean) b;
        int result = 0;

        if (g1.getGroup_id() > g2.getGroup_id()) {
            result = -1;
        } else if (g1.getGroup_id() < g2.getGroup_id()) {
            result = 1;
        }

        return result;
    }
}

GpReducer 类

package andy.group;

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

import java.io.IOException;

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

GropDriver 类

package andy.group;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
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 GropDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        if (args == null || args.length < 2)
            args = new String[]{"E:\\temp\\input\\group", "E:\\temp\\output"};
        Path path = new Path(args[1]);
        Configuration conf = new Configuration();
        FileSystem fs = path.getFileSystem(conf);
        Job job = Job.getInstance(conf);


        try {
            if (fs.exists(path))
                fs.delete(path, true);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } finally {
            fs.close();
        }


        job.setJarByClass(GropDriver.class);
        job.setMapperClass(GpMapper.class);
        job.setReducerClass(GpReducer.class);

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

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

		//设置分组器
        job.setGroupingComparatorClass(GpGoup.class);

        FileInputFormat.setInputPaths(job, args[0]);
        FileOutputFormat.setOutputPath(job, path);

        boolean b = job.waitForCompletion(true);

        System.exit(b ? 0 : 1);
    }
}

6 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实现步骤
(a)自定义一个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));
	}
}

(b)在Job驱动类中设置:

job.setCombinerClass(WordcountCombiner.class);
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值