接上篇文章,下面是reduce端的过程分析。
大概介绍下reduce的实际作用。以mapreduce经常做的groupby为例,map是将输入按group by的key排序,reduce就是做各种类型的聚合,比如sum,max,mean等。因此,可想而知,reduce的输入必须是按照groupby排序的,所以自然,reduce的输入必须汇聚所有map的输入,这也是reduce框架最复杂的部分。
首先,还是看一张reduce过程整体时序图:
1.从多个mapper端fetch数据。(copy)
fetch指从mapper端拉取mapper的输出数据。在fetch阶段reducetask会启动多个fetch线程从所有的mapper端取这个reducer的数据,同时还有一个fetchEvent线程负责获取mapper完成的event通知Fetch线程。
这里顺便提下,mapreduce的调度系统有一个假设:数据就近原则,也就是yarn的控制器会根据数据的位置分配mapper的位置,因此一般来说mapper的输入数据都是本地文件,再加上配置的机架感知和本地读策略mapper的输入处理效率是很高的,但reduce就不一样了,每个reducer的输入都要来自所有的mapper,因此,一般没办法优化reducer的位置。
reducer不会等到所有的mapper执行完毕再去拉数据,而是在mappertask完成一定比例后就会开始fetch,下面先列出一些fetch阶段的重要参数:
mapreduce.job.reduce.slowstart.completedmaps:在maptask完成了一定百分比后将触发fetch,默认为0.05
mapreduce.reduce.shuffle.read.timeout:fetch读数据的timeout时间,默认为3分钟
mapreduce.reduce.shuffle.maxfetchfailures:fetch最大的失败次数,默认为10次
mapreduce.reduce.shuffle.connect.timeout:fetch建立连接的timeout时间,默认也是3分钟
mapreduce.reduce.shuffle.parallelcopies:同时创建的fetch线程个数
fetch的目的有2个:内存buf和磁盘。那么如何判刑是保存进内存还是硬盘纳:首先判断内存buf大小有没有超过maxSingleShuffleLimit,如果没有则存入内存,如果整个内存buf都存满了则copy过程需要暂停一会儿(stallShuffle),其他情况则写入磁盘。代码如下:
if (!canShuffleToMemory(requestedSize)) {
LOG.info(mapId + ": Shuffling to disk since " + requestedSize +
" is greater than maxSingleShuffleLimit (" +
maxSingleShuffleLimit + ")");
return new MapOutput<K,V>(mapId, this, requestedSize, jobConf,
localDirAllocator, fetcher, true,
mapOutputFile);
}
if (usedMemory > memoryLimit) {
LOG.debug(mapId + ": Stalling shuffle since usedMemory (" + usedMemory
+ ") is greater than memoryLimit (&#