1.shuffle简图
2.shuffle细节图
分区用到了分区器,默认分区器是HashPartitioner
源码:
public class HashPartitioner<K2, V2> implements Partitioner<K2, V2> {
public void configure(JobConf job) {}
/** Use {@link Object#hashCode()} to partition. */
public int getPartition(K2 key, V2 value,
int numReduceTasks) {
return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
}
}
3.map端
-
每个map任务都有一个对应的环形内存缓冲区;输出是kv对,先写入到环形缓冲区(默认大小100M),当内容占据80%缓冲区空间后,由一个后台线程将缓冲区中的数据溢出写到一个磁盘文件
-
在溢出写的过程中,map任务可以继续向环形缓冲区写入数据;但是若写入速度大于溢出写的速度,最终造成100m占满后,map任务会暂停向环形缓冲区中写数据的过程;只执行溢出写的过程;直到环形缓冲区的数据全部溢出写到磁盘,才恢复向缓冲区写入
-
后台线程溢写磁盘过程,有以下几个步骤:
-
先对每个溢写的kv对做分区;分区的个数由MR程序的reduce任务数决定;默认使用HashPartitioner计算当前kv对属于哪个分区;计算公式:(key.hashCode() & Integer.MAX_VALUE) % numReduceTasks
-
每个分区中,根据kv对的key做内存中排序;
-
若设置了map端本地聚合combiner,则对每个分区中,排好序的数据做combine操作;
-
若设置了对map输出压缩的功能,会对溢写数据压缩
-
-
随着不断的向环形缓冲区中写入数据,会多次触发溢写(每当环形缓冲区写满100m),本地磁盘最终会生成多个溢出文件
-
合并溢写文件:在map task完成之前,所有溢出文件会被合并成一个大的溢出文件;且是已分区、已排序的输出文件
-
小细节:
-
在合并溢写文件时,如果至少有3个溢写文件,并且设置了map端combine的话,会在合并的过程中触发combine操作;
-
但是若只有2个或1个溢写文件,则不触发combine操作(因为combine操作,本质上是一个reduce,需要启动JVM虚拟机,有一定的开销)
-
4.reduce端
-
reduce task会在每个map task运行完成后,通过HTTP获得map task输出中,属于自己的分区数据(许多kv对)
-
如果map输出数据比较小,先保存在reduce的jvm内存中,否则直接写入reduce磁盘
-
一旦内存缓冲区达到阈值(默认0.66)或map输出数的阈值(默认1000),则触发归并merge,结果写到本地磁盘
-
若MR编程指定了combine,在归并过程中会执行combine操作
-
随着溢出写的文件的增多,后台线程会将它们合并大的、排好序的文件
-
reduce task将所有map task复制完后,将合并磁盘上所有的溢出文件
-
默认一次合并10个
-
最后一批合并,部分数据来自内存,部分来自磁盘上的文件
-
进入“归并、排序、分组阶段”
-
每组数据调用一次reduce方法
5.总结
-
map端
-
map()输出结果先写入环形缓冲区
-
缓冲区100M;写满80M后,开始溢出写磁盘文件
-
此过程中,会进行分区、排序、combine(可选)、压缩(可选)
-
map任务完成前,会将多个小的溢出文件,合并成一个大的溢出文件(已分区、排序)
-
-
reduce端
-
拷贝阶段:reduce任务通过http将map任务属于自己的分区数据拉取过来
-
开始merge及溢出写磁盘文件
-
所有map任务的分区全部拷贝过来后,进行阶段合并、排序、分组阶段
-
每组数据调用一次reduce()
-
结果写入HDFS
-