map的输出会经过一个名为shuffle的过程交给reduce处理, 当然也有map的结果经过sort-merge交给reduce处理的。其实在MapReduce流程中, 为了 让 reduce可以并行处理map结果, 必须对map的输出进行一定的排序和分割 , 然后再交给对应的 reduce, 而这个将map输出进行进 一步整理并交给reduce的过程就成 为了shuffle。从shuffle的过程中可以看出, 它是 Map Reduce的核心所在, shuffle过程的性能与整个MapReduce的性能直接相关。
总体来说, shuffle过程包含在map和 reduce两端中。在map端的shuffle过程是对map的结果进行划分(partition)、 排序(sort)和分割(spill),然后将属于同一个划分的输出合并在一起(merge), 并写在磁盘上, 同时按照不同的划分将结果发送给对应的reduce (map 输出的划分与 reduce的 对应关系由JobTracker确定〉。reduce端又会将各个map送来的属于同一个划分的输出进行合并(merge), 然后对merge的结果进行排序, 最后交给reduce处理。 下面将从map和reduce两端详细介绍shuffle过程。
1.Map端
从MapReduce的程序中可以看出, map的输出结果是由collector处理的, 所以map端 的shuffle过程包含在collect函数对map输出结果的处理过程中。 下面从具体的代码来分析 map端的shuffle过程。首先从collect函数的代码入手。 从下面的代码段可以看出 map函数的输出内存缓冲区是一个环形结构。
final int kvnext = (Kcvindex + 1 )%kvoffsets.length;
当输出内存缓冲区内容边到设定的阙值时, 就需要把缓冲区内容分割(spill)到磁盘中了。 但是在分割的时候map并不会阻止继续向缓冲区中 写入结果, 如果 map结果生成的速度快于写出速度, 那么缓冲区会写满, 这时 map任务必须等待, 直到分割 写出过程结束。 这 个过程可以参考下面的代码。

在collect函数中将缓冲区中的内容写出时会调用sortAndSpill函数。sortAndSpill函数每被调用一次就会创建一个spill文件,然后按照key值对需要写出的数据进行排序,最后按照划分的顺序将所有需要写出的结果 写入这个spill文件中。 如果用户作业配置了 combiner 类, 那么在写出过程中会先 调用 combineAndSpill()再写 出, 对结果进行进一步的合并 (combine)是为了让map的输出数据更加紧凑。sortAndSpill函数的执行过程可以参考下面 sortAndSpill函数的代码。

显然,直接将每个map生成的众多spill文件(因为map过程中,每一次缓冲区写出都会产生一个spill文件)交给reduce处理不现实。所以在每个map任务结束之后在map的 Task Tracker上还会执行合并操作(merge),这个操作的主要目的是将map生成的众多spill 文件中的数据按照划分重新组织,以便于reduce处理。主要做怯是针对指定的分区,从各个 spill文件中拿出属于同一个分区的所有数据,然后将它们合并在一起,并写入一个已分区且已排序的map输出文件中。这个过程的详细情况请参考mergeParts()函数的代码,这里不再列出。
待唯一的已分区且已排序的map输出文件写入最后一条记录后,map端的shuffle阶段就结束了下面就进入reduce端的shuffle阶段。
2.reduce阶段
在reduce端,shuffle阶段可以分成三个阶段:复制map输出、排序合并、reduce处理。 下面按照这三个阶段进行详细介绍。
如前文所述,map任务成功完成后,会通知父TaskTracker状态已更新,进而 TaskTracker通知JobTracker(这些通知在心跳机制中进行)。所以,对于指定作业来说, Job Tracker能够记录map输出和TaskTracker的映射关系。reduce会定期JobTracker获取 map的输出位置。一且拿到输出位置,reduce任务就会从此输出对应的TaskTracker上复制输出到本地(如果map的输出很小,则会被复制到执行reduce任务的TaskTracker节点的内存中,便于进一步的处理,否则会放入磁盘),而不会等到所有的map任务结束。这就是reduce任务的复制阶段。
在reduce复制map的输出结果的同时,reduce任务就进入了合并(merge)阶段。这一阶段主要的任务是将从各个mapTaskTracker上复制的map输出文件〈无论在内存还是在磁盘〉进行整合,并维持数据原来的顺序。
reduce端的最后阶段就是对合并的文件进行reduce处理。reduceTaskTracker从合并的文件中按照顺序先拿出一条数据,交给reduce函数处理,然后直接将结果输出到本地的 HDFS上(因为在Hadoop集群上,TaskTracker节点一般也是DataNode节点儿接着继续拿出下一条数据,再进行处理。下面是reduceTask上run函数的部分代码,从这个函数可以看出整个reduce端的三个步骤。

shuffle过程的优化
熟悉了上面介绍的shuffle过程,可能有读者会说:这个shuffle过程不是最优的。是的,Hadoop采用的shuffle过程并不是最优的。举个简单的例子,如果现在需要Hadoop集群完成两个集合的并操作,事实上并操作只需要让两个集群中重复的元素在最后的结果中出现一次就可以了,并不要求结果的元素是按顺序排列的。但是如果使用Hadoop默认的shuffle过程,那么结果势必是排好序的,显然这个处理就不是必须的了。在这里简单介绍从Hadoop参数的配置出发来优化shuffle过程。在一个任务中,完成单位任务使用时问最多的一般都是i/0操作。在map端,主要就是shuffle阶段中缓冲区内容超过阈值后的写出操作。可以通过合理地设置ip.sort.*属性来减少这种情况下的写出次数,具体来说就是增加io.sort.mb的值。在reduce端,直接在复制map输出的时候将复制的结果放在内存中同样能够提升性能,这样可以让部分数据少做两次i/0操作(前提是留下的内存足够reduce任务执行)。所以在reduce函数的内存需求很小的情况下,将mapred.inmem.merge.threshoJd设置为0,将mapred.job.reduce. input. buffer. percent设置为1.0(或者一个更低的值〉能够让I/0操作更少,提升shuffle的性能。

1万+

被折叠的 条评论
为什么被折叠?



