5.4 规约(Combine)

2016-03-09 22:45:07 4,228 0

一 规约概念讲解

在WordCount案例中,我们TokenizerMapper的输出部分代码如下所示

while (itr.hasMoreTokens()) {
	String nextToken = itr.nextToken();		
	word.set(nextToken);
	context.write(word, one);
}

即将每一行中的内容分割为单独的单词之后,就立即写出。

但是想一下,如果我们同时有多个Mapper任务在运行。每个Mapper处理各自负责的内容,如果我们在每个Mapper内部进行局部汇总之后,再将多个Mapper任务的输出合并到Reducer中,是不是效率更高呢?

关于局部汇总概念,通过案例来讲解,假设我们的word.txt中的内容为

hello you
hello me
hello he
hello she

mapper1负责处理1、2行,mapper2负责处理3、4行。

在没有进行局部汇总的情况下

mapper1输出

<hello,1>
<you,1>
<hello,1>
<me,1>

mapper2输出

<hello,1>
<he,1>
<hello,1>
<she,1>

在进行了局部汇总的情况下:

mapper1输出

<hello,2>
<you,1>
<hello,1>

mapper2输出


<hello,2>
<he,1>
<she,1>

相信到这里读者已经看出来规约的作用,即在Mapper端先做局部聚合,把相同key的value合并在一起

这样做的好处是:

数据进行规约处理,数据量变小了,传送到reduce端的数据量变小了,传输时间变短,作业的整体时间变短。


二 规约实现

我们要知道的是,规约是对Mapper端的输出先做局部聚合。要实现规约很简单,Job对象有一个方法:

void setCombinerClass(Class<? extends Reducer> cls)

这个方法是接受一个Reducer参数,如果我们设置了这个方法,即可以进行规约。在我们的案例中,按照如下方法即可以实现规约

job.setCombinerClass(IntSumReducer.class);

问题:为什么规约接受的是一个Reducer呢?

因为我们在Reducer中进行聚合操作,而如果Mapper端的局部聚合操作与Reducer端的聚合方式一样的话,我们当然可以直接设置规约的类型为Reducer。

运行WordCountApp,查看控制台输出,我们关注的是计数器组Map-Reduce Framework中以下计数器

Map input records=2
Map output records=4
Combine input records=4
Combine output records=3
Reduce input records=3
Reduce output records=3

mapper的输出和输出与没设置规约之前是一样的(对比计数器章节),但是Combine的接受了Mapper的output,因此input是4个,进行规约之后,output是3条记录。而reducer端接受combine的output,因此input是3个,输出不变。


总结:

Combiner发生在Map端,对数据进行局部聚合处理,数据量变小了,传送到reduce端的数据量变小了,传输时间变短,作业的整体时间变短。