在每个maptask的结束,我们拿到的是<K,V>的队列,在Reduce中,输入的是<K,Iterable V>。在中间有一个被称为Shuffle的工作,将Maptask的数据按Key排序。其主要的工作,大体上讲1.完整地从map task端拉取数据到reduce端。2.在跨节点拉取数据时,尽可能地减少对带宽的不必要消耗。3.减少磁盘IO对task执行的影响。(主要的优化工作在于更多的使用内存而非磁盘IO)


MapTask的Shuffle

从四个方面来讲Map这块的Shuffle,map结果写入磁盘 ; 缓存数据的分区(Partition)分组(Combiner)排序(Sort) ; 文件合并 (Merge); 

1.map结果写入磁盘

对于并行的map工作,产生了许多结果输出,首先我们讲各个节点上得到的结果存到一个环形内存缓冲区里(填到80%就往外拿数据了,不然满了,剩下20%继续读map的数据),当缓冲区全满了,那map就阻塞吧,直到写磁盘结束了再继续。写出去的文件叫做溢出文件(Spill),这个溢写是由单独线程来完成,不影响往缓冲区写map结果的线程,溢写线程启动时不应该阻止map结果的输出。

2.缓存数据的分区(Partition)分组(Combiner)排序(Sort)

首先是分区(Partition):我们拿到数据之后,需要知道这个map得到的<k, iteration V>要到哪个reduceTask里去完成。MapReduce提供Partitioner接口,它的作用就是根据key或value及reduce的数量来决定当前的这对输出数据最终应该交由哪个reduce task处理。默认对key hash后再以reduce task数量取模,默认的取模方式只是为了平均reduce的处理能力,如果用户自己对Partitioner有需求,可以订制并设置到job上。

然后是分组(Combiner):拿WordCount举例子,会产生两个<Hello,1><Hello,1>这样,在传输的时候一个maptask里的数据传两个<Hello,1>过去reducetask,过于浪费了,不如变成<Hello,2>再传过去。Combiner和reducer很像(其实是reducer是Combiner…),reduce是从<K1,V1>变成<K2,V2>,combiner的区别就是从<K,V>变成<K,V>

最后讲排序(Sort):spill 线程在把缓冲区的数据写到磁盘前,会对它进行一个二次快速排序,首先根据数据所属的partition 排序,然后每个partition 中再按Key 排序。输出包括一个索引文件和数据文件。如果设定了Combiner,将在排序输出的基础上运行。

3.文件合并(Merge)

每次spill会在磁盘上生成一个spill文件,如果map的输出结果真的很大,有多次这样的spill发生,磁盘上相应的就会有多个spill文件存在,当map task真正完成时,内存缓冲区中的数据也全部溢写到磁盘中形成一个溢写文件。最终磁盘中会至少有一个这样的溢写文件存在(如果map的输出结果很少,当map执行完成时,只会产生一个溢写文件),因为最终的文件只有一个,所以需要将这些溢写文件归并到一起,这个过程就叫做Merge。

ReduceTask的Shuffle

Reduce阶段的Shuffle可以分为三个步骤来理解:从Map拿数据 ; 归并数据 ; 

1.从Map拿数据 

 在map的最后所有文件Merge成了一个,reducetask从maptask这里拿到这个文件(map有快有慢,好了就拿),如果文件小,就直接存JVM里,大的话也通过溢写来操作,也可通过Combiner进行合并

2.归并数据

 拿到了数据与拿到了内存里的数据与溢写文件,要整理成一个文件,所以除了在不同的maptask拿到数据之后的归并,最后还需要一次归并,把所有的<K,Iterable V>归并到一块。最后默认写到文件中,得到Reducer需要的<K, Iter V>