MapReduce确保每个reducer的输入都是按键排序的,系统执行排序的过程称为shuffle。  在map端,map函数开始产生输出时,并不是直接写入到磁盘,而是利用缓冲的方式写到内存并出于效率的考虑进行预排序,具体过程:每个map任务都有一个环形内存缓冲区用于存储任务输出,该缓冲区默认大小为100m,可以通过io.sort.mb属性调整该值,一旦缓冲达到阈值io.sort.spill.percent,默认0.8,一个后台线程便开始把内容溢出到spill磁盘,在spill之前,线程首先根据数据最终要传的reducer把数据划分成相应的分区partition,在每个partition中,后台线程按键进行内排序,如果有combiner,他就在排序后的输出上运行,运行combiner使得map输出结更加紧凑,因此减少写到磁盘的数据和传递给reducer的数据。

       在spill过程中,map输出继续写到缓冲区,若此时缓冲区写满,map会阻塞直到spill过程完成,spill过程按轮询方式将缓冲区中的内容写到mapred.local.dir属性指定的作业特定子目录中的目录中。

       每次内存缓冲区达到溢出阈值就会新建一个溢出文件spill file,在任务完成之前,溢出文件被合并成一个或多个已分区且已排序的输出文件,属性io.sort.factor决定一次最多合并多少流,默认值是10。若合并后还至少存在3个溢出文件,则combiner就会在输出文件写到磁盘之前再次运行,不会影响最终结果,如果只有1,2个溢出文件,那么对map输出减少方面没有必要调用combiner,不会为该map输出再次运行combiner。

       在将压缩map输出写到磁盘的过程中,对她进行压缩,会使得写磁盘速度更快,节约磁盘空间,并且减少传给reducer的数据量。

       reducer通过HTTP方式得到输出文件的分区。

       在reduce端,map输出文件位于运行map任务的tasktracker的本地磁盘,tasktracker需要为分区文件运行reduce任务,reduce任务需要集群上若干个map任务的map输出作为特殊的分区文件,每个map任务的完成时间不同,因此只要有一个map完成,reduce就会开始复制其输出,reduce任务有少量复制线程,因此能并行取得map输出,默认值是5个线程,可通过属性mapred.reduce.parallel.copies来设置。

       reduce如何知道从哪台机器上取得map的输出:map任务成功完成后,他们会通知其父tasktracker状态已更新,然后tasktracker进而通知jobtracker,在MapReduce2中任务直接通知其Application Master,通知在心跳通信机制中传输。因此,对于指定作业,jobtracker或Application Master知道map输出和tasktracker之间的映射关系。reducer中的一个线程定期询问jobtracker或Application Master以便获取map输出的位置,直到获得所有输出位置。tasktracker不会再第一个reducer检索到map输出时就立即从磁盘上删除他们,因为第一个reducer可能会失败,tasktracker会等待,直到jobtracker告知他们删除map输出,这是作业完成后执行的。

       如果map输出相当小,会被复制到reduce任务JVM的内存,缓冲区大小由mapred.job.shuffle.input.buffer.percent属性控制,指定用于此用途的堆空间的百分比,否则map输出被复制到磁盘上。一旦内存缓冲区达到阈值大小,由mapred.job.shuffle.merge.percent决定,或达到map输出阈值,由mapred.inmem.merge.threshold控制,则合并后溢出写到磁盘中。如果指定combiner,则在合并期间运行它以降低写入磁盘的数据量。

       随着磁盘上副本增多,后台线程会将他们合并为更大的排好序的文件,为后面的合并节省时间,此时,为了合并会将压缩了的map输出结果在内存中进行解压缩。

       复制完所有map输出后,reduce任务进入合并阶段,在这个阶段合并map输出,维持其顺序排序,该过程循环进行。比如有50个map输出,合并因子默认为10,由io.sort.factor属性设置,则合并进行5趟,最后有5个中间文件。在最后阶段也就是执行reduce函数阶段,直接把数据输入reduce函数,而不是把5个中间文件再合并为一个已排序的文件,这样可以节省一次磁盘往返行程。最后的合并可以来自内存和磁盘片段。

       上述合并并不是实际所进行的,实际真正进行的合并过程:目标是合并最小数量的文件以满足最后一趟的合并系数。如果有40个文件,第一趟合并4个,后面3趟没趟合并10个,因此,第一趟的合并结果是1个,后面三趟的合并结果是3个,加上剩下的6个未合并的文件,总共10个,最后一趟就是将这10个合并到reduce。

       上述过程也合并了四次,并没有减少合并次数,但是这是一种优化措施,目的是尽量减少写到磁盘的数据量,因为最后一趟总是直接合并到reduce。两种合并方式对比:合并4趟,每次合并10个,那么若达到缓冲区大小就要溢出写到磁盘,而合并4个就不需要溢出写到磁盘,

       在reduce阶段,对已排序输出中的每个键调用reduce函数,此阶段的输出直接写到文件系统,一般为HDFS,如果采用HDFS,由于tasktracker节点也运行数据节点,所以第一个块副本将被写到本地磁盘。