更新:以hadoop2.7.2版本重新修改
Reducer类在执行的时候,通常包含3个阶段,SHUFFLE,SORT,REDUCE。具体可参考TaskStatus中的内部类Phase定义。
Shuffle阶段,主要是把map阶段写到本地磁盘的输出,通过网络,拉到执行reduce节点的本地。该阶段代码主要在ReduceTask这个类中。
这个是由ReduceTask决定的。理论上,Reducetask启动后,shuffle进程就会起来。
参数mapreduce.job.reduce.slowstart.completedmaps(位于MRJobConfig类中,mr相关的参数大部分位于该类),该值默认是0.05f。
Reduce在启动之前,会计算已经完成的map数占所有map数的比例,只有大于设置的比例(默认0.05)时,Reduce才会启动。
具体逻辑见下代码:RMContainerAllocator
int completedMapsForReduceSlowstart = (int)Math.ceil(reduceSlowStart *
totalMaps);
if(completedMaps < completedMapsForReduceSlowstart) {
LOG.info("Reduce slow start threshold not met. " +
"completedMapsForReduceSlowstart " +
completedMapsForReduceSlowstart);
return;
} else {
LOG.info("Reduce slow start threshold reached. Scheduling reduces.");
setIsReduceStarted(true);
}
Shuffle的主要实现在ShuffleConsumerPlugin。在Hadoop2中,该类是个接口,便于用户扩展。当然,大部分用户不会主动去扩展,用的是该接口的一个
默认实现类Shuffle。
数组的大小取决于mapreduce.reduce.shuffle.parallelcopies,默认为5。每个Fecher内部其实是通过http去pull map结果所在节点的数据。
问题:哪些map数据放到内存?哪些放到磁盘?
Shuffle阶段,主要是把map阶段写到本地磁盘的输出,通过网络,拉到执行reduce节点的本地。该阶段代码主要在ReduceTask这个类中。
1,Shuffle的启动实机
这个是由ReduceTask决定的。理论上,Reducetask启动后,shuffle进程就会起来。
参数mapreduce.job.reduce.slowstart.completedmaps(位于MRJobConfig类中,mr相关的参数大部分位于该类),该值默认是0.05f。
Reduce在启动之前,会计算已经完成的map数占所有map数的比例,只有大于设置的比例(默认0.05)时,Reduce才会启动。
具体逻辑见下代码:RMContainerAllocator
int completedMapsForReduceSlowstart = (int)Math.ceil(reduceSlowStart *
totalMaps);
if(completedMaps < completedMapsForReduceSlowstart) {
LOG.info("Reduce slow start threshold not met. " +
"completedMapsForReduceSlowstart " +
completedMapsForReduceSlowstart);
return;
} else {
LOG.info("Reduce slow start threshold reached. Scheduling reduces.");
setIsReduceStarted(true);
}
2,获取map输出结果
Shuffle的主要实现在ShuffleConsumerPlugin。在Hadoop2中,该类是个接口,便于用户扩展。当然,大部分用户不会主动去扩展,用的是该接口的一个
默认实现类Shuffle。
2.1 怎么取结果
取结果的主要实现是Fetcher类,该类继承自Thread。为了加快shuffle速度,Shuffle内部会构造一个Fetcher数组,多线程去拷贝数据。数组的大小取决于mapreduce.reduce.shuffle.parallelcopies,默认为5。每个Fecher内部其实是通过http去pull map结果所在节点的数据。
2.2 存结果
2.1步骤取到的结果会存在内存或者磁盘中。主要的存取逻辑在代码MergeManagerImpl中。问题:哪些map数据放到内存?哪些放到磁盘?
放map结果之前,会调用canShuffleToMemory方法,判断拿到的map结果是否可以放入内存
private boolean canShuffleToMemory(long requestedSize) {
return (requestedSize < maxSingleShuffleLimit);
}
requestedSize 是pull的map结果大小,
return (requestedSize < maxSingleShuffleLimit);
}
requestedSize 是pull的map结果大小,
maxSingleShuffleLimit,计算逻辑如下:
this.maxSingleShuffleLimit = (long)(memoryLimit * singleShuffleMemoryLimitPercent);
memoryLimit计算逻辑如下
this.memoryLimit = (long)(jobConf.getLong(MRJobConfig.REDUCE_MEMORY_TOTAL_BYTES,Runtime.getRuntime().maxMemory()) * maxInMemCopyUse);
maxInMemCopyUse取决于参数mapreduce.reduce.shuffle.input.buffer.percent,默认0.70f;
MRJobConfig.REDUCE_MEMORY_TOTAL_BYTES取决于参数mapreduce.reduce.memory.totalbytes,默认值其实是reduce进程当前最大可用内存。
另外singleShuffleMemoryLimitPercent取决于参数mapreduce.reduce.shuffle.memory.limit.percent,默认为0.25f
故map默认的最大结果= reduce当前可用最大内存*0.75*0.25 :
this.maxSingleShuffleLimit = (long)(memoryLimit * singleShuffleMemoryLimitPercent);
memoryLimit计算逻辑如下
this.memoryLimit = (long)(jobConf.getLong(MRJobConfig.REDUCE_MEMORY_TOTAL_BYTES,Runtime.getRuntime().maxMemory()) * maxInMemCopyUse);
maxInMemCopyUse取决于参数mapreduce.reduce.shuffle.input.buffer.percent,默认0.70f;
MRJobConfig.REDUCE_MEMORY_TOTAL_BYTES取决于参数mapreduce.reduce.memory.totalbytes,默认值其实是reduce进程当前最大可用内存。
另外singleShuffleMemoryLimitPercent取决于参数mapreduce.reduce.shuffle.memory.limit.percent,默认为0.25f
故map默认的最大结果= reduce当前可用最大内存*0.75*0.25 :
1)当pull的map结果大小>该值,数据放入磁盘。
2)当pul的map结果大小<该值,当前已用的内存是否超过了内存限制,没超过,放内存
2)当pul的map结果大小<该值,当前已用的内存是否超过了内存限制,超过了,等待.
关于上面的参数,如果想看reducetask计算时的实时值,可以通过log,如下:
2017-04-20 11:49:03,213 INFO [main] org.apache.hadoop.mapreduce.task.reduce.MergeManagerImpl: MergerManager: memoryLimit=802632512, maxSingleShuffleLimit=200658128, mergeThreshold=529737472, ioSortFactor=10, memToMemMergeOutputsThreshold=10