今天学习的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的数据全部拉去过来再去执行---效率比较低
















