MapReduce(十)Shuffle和Sort
来源:Hadoop权威指南
MapReduce确保每一个reducer的输入都是依照key排序的。系统执行排序然后传输map的输出给reducer的过程被称为shuffle。本文中,我们将讲解shuffle是如何工作的,这些内容对于你优化MapReduce会有所帮助。
Map端
当map函数开始输出数据时,它并非简单地将输出写入磁盘。它会利用内存缓冲以及一些预排序来提升性能。下图展示了这个过程:

每个map任务都会有一个循环缓冲区,map函数的输出会首先写到这个缓冲区中。这个缓冲区默认大小为100MB(通过调整mapreduce.task.io.sort.mb属性可以调整这个大小)。当缓冲区的内容大小超过某个阈值时(mapre
duce.map.sort.spill.percent, 默认值为 0.80, or 80%),一个后台线程没开始将这些内容写入磁盘,这个过程被称为spill。在spill的同时,map函数的输出会持续地写入缓冲区,不过如果缓冲区满了,map函数就会被阻塞直到spill完成为止。Spill会以round-robin轮询的方式写入到由mapreduce.cluster.local.dir property指定的位于job的目录的子目录中。
在上述的后台线程将数据写入磁盘之前,该线程会将数据分区。在每个分区中,后台线程会执行一个在内存中基于key的排序,如果用户指定了combiner函数,它会基于排好序的数据作为输入执行。Combiner可以产生更紧凑的map输出,也因此有更少的数据写入磁盘以及在网络中传输。
每次内存缓冲区接近spill阈值时,一个新的spill文件就会被创建出来,所以在map任务写完最后一个输出后,将会有多个spill文件。在任务完成之前,这些spill文件会合并进入一个分区的已排序的输出文件中。配置属性mapreduce.task.io.sort.factor控制着一次性合并的最大文件数,默认值为10.
如果至少有3个spill文件(由配置属性mapreduce.map.combine.minspills指定),在文件写入磁盘之前,combiner将会再次运行。需要记住,combiner可能会执行多次而且不会影响最终结果。如果只有一个或两个spill文件,combiner因为负载原因被决定不会执行。
spill合并后的文件的分区将会被告知给reducer(基于HTTP协议),用来提供文件分区的最大的worker线程数由配置属性mapreduce.shuffle.max.threads指定。这个配置是针对整个节点而不是单独一map任务的。该值的默认值是0,含义是使用机器的CPU数量的两倍的线程。
Reduce端
map的输出位于执行map任务的机器的磁盘上(尽管map总是将输出写到本地磁盘,但reduce并非如此),然后现在它需要被运行reduce任务的机器获取到。此外reduce任务需要跨越集群获取到map的输出。map任务会在不同的时间点完成,所以一旦有完成的map,reduce会马上拷贝它的输出。这就是reduce任务的copy阶段。reduce任务会启动少量的复制线程并行地拉取map的输出,默认的拷贝线程数目为5,不过这个数量可以通过设置mapreduce.reduce.shuffle.parallelcopies属性来改变。
Reducer是如何知晓到哪台机器获取map的输出的?当map任务完成后,它们会通过心跳机制通知它们的application master。因此,给定一个任务,application master知晓map输出和主机间的映射关系。reducer上的一个线程会周期性地向master询问这些映射关系直到它获取到了全部的数据。当一个reducer获取了数据后,map不会马上删除输出数据,因为reducer可能会失效重启,取而代之的是它们会等待application master的通知才会删除,这发生在job完成后。
Map的输出如果足够小(buffer大小由属性mapreduce.reduce.shuffle.input.buffer.percent控制,指定百分之几的堆内存用于缓冲数据),它们会被拷贝到reduce的内存中;否则,它们会被拷贝在到磁盘上。当内存缓存达到一个阈值时(由mapreduce.reduce.shuffle.merge.percent控制)或者map输出数量达到一个阈值时(由mapreduce.reduce.merge.inmem.threshold控制),它会被合并然后输出到磁盘中。如果指定了combiner,它们会在merge时被执行以减少写入磁盘的数据量。
随着拷贝在磁盘上不断地累积,reducer有一个后台程序会将它们合并成一个个大的排好序的文件,这将节省后面的合并时间。
当所有的map的输出全部拷贝完毕,reduce任务开始进入sort阶段(这个阶段准确地来说其实已经改叫merge阶段,因为排序操作其实是在map端执行的),这个阶段会合并map的输出,维护它们的顺序。merge阶段需要几轮才能完成。比如说如果由50个map输出以及merge因子是10(默认值,由mapreduce.task.io.sort.factor控制),每一轮会合并10个文件称为1个文件,然后最终会得到5个中间文件。
reducer并没有采用最后一轮将中间的merge好的文件再合并成一个排好序的文件的这个策略,取而代之的是,merge节省掉这最后一轮的磁盘操作,而直接将数据发射给reduce函数。这个最终的merge可以充分利用混合着内存和磁盘中的段。

浙公网安备 33010602011771号