今天学习的Combiner合并在MapReduce中是可选项,默认情况下Combiner不会执行,它就像一个可插拔的小组件。Combiner合并相当于MR的一种优化策略

一、概念

1)Combiner是MR程序中Mapper和Reducer之外的一种组件

2)Combiner组件的父类就是Reducer

3)Ccombiner和reducer的区别在于运行的位置:

        Combiner是在每一个maptask所在的节点运行

        Reducer是接收全局所有Mapper的输出结果;

4)Combiner的意义就是对每一个maptask的输出进行局部汇总,

5)Combiner能够应用的前提是不能影响最终的业务逻辑,而且,combiner的输出kv应该跟reducer的输入kv类型要对应起来。一般适用于累加类型的操作

例:

Mapper

3 5 7 ->(3+5+7)/3=5

2 6 ->(2+6)/2=4

Reducer

(3+5+7+2+6)/5=23/5    不等于    (5+4)/2=9/2

这样的话,Combiner合并影响了业务逻辑

二、自定义Combiner

Combiner是在map阶段执行结束 reducer任务执行之前

         其中combiner的输入是Mapper的输出key value

         combiner的输出是reducer的输入key value

public class MyCombiner extends Reducer<Text, LongWritable,Text,LongWritable> {
    @Override
    protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException {
        Iterator<LongWritable> iterator = values.iterator();
        long num = 0l;
        while (iterator.hasNext()){
            LongWritable next = iterator.next();
            long l = next.get();
            num += l;
        }
        context.write(key,new LongWritable(num));
    }
}

接着,我们在Driver类中添加如下代码:

job.setCombinerClass(MyCombiner.class);

        这样运行之后,Mapper执行的结果先提交给Combiner处理合并,这样Reducer处理起来非常的快。我们再来看Reducer类的代码逻辑结构:

class WCReducer extends Reducer<Text,LongWritable,Text,LongWritable>{
    @Override
    protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException {
        Iterator<LongWritable> iterator = values.iterator();
        long sum = 0l;
        while (iterator.hasNext()){
            sum += iterator.next().get();
        }
        context.write(key,new LongWritable(sum));
    }
}

简直和我们Combiner的处理逻辑一模一样呀!!

所以更方便的我们可以这样在Driver类中写:(此时就不需要再定义MyCombiner类了)

job.setCombinerClass(WCReducer.class);

执行结果也是正确的!

总结一下:

 Combiner的两种使用方式:Combiner和分区还有排序不一样,Combiner是可选的操作 但是分区排序是必选的

 1、自定义combiner类,继承Reducer逻辑,然后在driver中通过job.setCombinerClass()

 2、如果combiner类和reduce类的处理逻辑一样(一般情况下:两个的处理逻辑都是一样) 此时我们就可以直接在driver中设置combiner类就是reducer

 但是注意 虽然代码一样 但是combiner类和reduce类执行的逻辑不一样

三、案例

我们将一个大小为2.14KB的文件分成三个切片?每个切片大小为多少字节?

2.14*1024/3=730.4533333......           我们给每个切片750字节就好了

//调节切片的个数为3个  Math.max(minSize,Math.min(maxSize,blockSize))
        FileInputFormat.setMaxInputSplitSize(job,750);

接下来我们步入正题:

public class FlowCombiner extends  Reducer<Text,FlowBean, Text,FlowBean> {
    @Override
    protected void reduce(Text key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException {
        Iterator<FlowBean> iterator = values.iterator();
        int upSum=0;
        int downSum = 0;
        while (iterator.hasNext()){
            FlowBean bean = iterator.next();
            upSum+=bean.getUpFlow();
            downSum+=bean.getDownFlow();
        }
        int sum = upSum+downSum;
        FlowBean flowBean = new FlowBean();
        flowBean.setPhone(key.toString());
        flowBean.setUpFlow(upSum);
        flowBean.setDownFlow(downSum);
        flowBean.setSumFlow(sum);
        context.write(key,flowBean);
    }
}

随后在Driver类里添加:

job.setCombinerClass(FlowCombiner.class);

        此处不可以调用Reducer.class,原因:此处FlowCombiner.class类和Reducer.class类的输出类型不一样,影响逻辑。

Combiner注意点

 1、combiner执行时机在mapper结束 reducer执行之前

 2、combiner操作可选择,可以添加也可以不要,不要的话: map阶段的数据直接给reducer

         如果加上这个操作 那么map阶段的数据先给combiner 再从combiner给reducer

         同时注意map阶段的输出就是combiner的输入 combiner的输出就是reducer阶段的输入

 3、combiner和reducer逻辑非常像,都是根据key值,把相同key值的value集中起来走一次reducer。不同点在于combiner对每一个maptask都会去执行一次, 而reducer是将每一个maptask的数据全部拉去过来再去执行---效率比较低