官方给的定义:系统执行排序、将 map 输出作为输入传给 reducer 的过程称为 Shuffle。 (看完是不是一脸懵逼)通俗来讲,就是从 map 产生输出开始到 reduce 消化输入的整个 过程称为 Shuffle。如下图用黑线框出的部分:
每一个map任务都会有一个圆形缓冲区。默认大小100MB(io.sort.mb 属性)阈值 0.8 也就是 80MB(mapreduce.map.sort.spill.percent 属 性指定) ,
一 旦 达 到 阈 值 一 个 后 台 线 程 开 始 把 内 容 写 到 (spill) 磁 盘 的 指 定 目 录 mapred.local.dir 下的新建的一个溢出写文件。写入磁盘前先 partition、sort、 [combiner]。一个 map task 任务可能产生 N 个磁盘文件。map task 运算完之后,产 生了 N 个文件,然后将这些文件 merge 合成一个文件。 如果 N=2,合成的新文件写入磁盘前只经过 patition(分区)和 sort(排序)过程,不 会执行 combiner 合并(无论是否指定 combiner 类),如下图所示
如果 N>=3,合成的新文件写入磁盘前经过 patition(分区)、sort(排序)过和 combiner 合并(前提是指定了 combiner 类),如下图所示:
思考:为什么只有当 N>=3 时,合成文件才会执行 combiner 呢?
这是因为如果 N<3 时,执行 combiner 虽然减少了文件的大小,但是同时产生了一定的系 统开销。由于减少的文件大小不大,权衡利弊后,确定 N<2 时不在执行 combiner 操作。 当该 map task 全部执行完之后,对应的 reduce task 将会拷贝对应分区的数据(该过 程称为 fetch),如下图所示:
其它的 map task 任务完成后,对应的 reduce task 也同样执行 fetch 操作,如下图所 示:
每个 map 任务的完成时间可能不同,因此只要有一个任务完成,reduce 任务就开始复制 其输出。该阶段被称为 reduce 的复制阶段。reduce 任务有少量复制线程,因此能够并行 取 得 map 输 出 。 默 认 值 是 5 个 线 程 , 但 这 个 默 认 值 可 以 通 过 设 置 mapred.reduce.parallel.copies 属性改变。
复制完所有 map 输出后,reduce 任务进入合并阶段,该阶段将合并 map 输出,并维持其 顺序排序(相当于执行了 sort),如果指定了 combiner,在写入磁盘前还会执行 combiner 操作。
那么具体是如何合并的呢? 合并因子默认是 10,可以通过 io.sort.factor 属性设置。合并过程是循环进行了,可能叫经过多趟合并。目标是合并最小数量的文件以便满足最后一趟的合并系数。假设有 40 个文件,我们不会在四趟中每趟合并 10 个文件从而得到 4 个文件。相反,第一趟只合并 4 个文件,随后的三趟分别合并 10 个文件。再最后一趟中 4 个已合并的文件和余下的 6 个(未 合并的)文件合计 10 个文件。具体流程如下图所示: