Shuffle

Shuffle过程包含在Map和Reduce两端中,是MapReduce的核心所在。

一、Map端

在Map端,Shuffle过程是对Map的结果进行划分(partition)、溢写(spill),然后将属于统一个划分的输出合并(merge)在一起。其主要流程如下图所示:

整个流程分为四步。每个Map Task都有一个内存缓冲区,存储着map的输出结果,当缓冲区快满的时候需要将缓冲区的数据以一个临时文件的方式存放到磁盘,当整个Map Task结束后再对磁盘中这个Map Task产生的所有临时文件做合并,生成最终的正式输出文件,然后等待Reduce Task来拉数据。

1. 在Map Task执行时,它的输入数据来源于HDFS的block。在MapReduce概念中,Map Task只读取split,Split与block的对应关系可能是多对一,也可以是一对一。

2. 在经过mapper的运行后,输出的结果是一个<key, value>对,不同的<key, value>对会因为key的不同划分到不同的Reduce Task里去进行合并操作。这个划分操作是由partition进行的。

MapReduce提供Partitioner接口,它的作用就是根据key和reduce的数量来决定当前的这对输出数据最终应该交由哪个Reduce Task处理。默认对key hash后再以Reduce Task数量取模。默认的取模方式只是为了平均reduce的处理能力,如果用户自己对Partitioner有需求,可以订制并设置到job上。 

3.接下来,需要将数据写入内存缓冲区中,缓冲区的作用是批量收集map结果,减少磁盘IO的影响。<key, value>对以及Partition的结果都会被写入缓冲区。当然写入之前,key与value值都会被序列化成字节数组。 

整个内存缓冲区就是一个字节数组。这个内存缓冲区是有大小限制的,默认是100MB,可以通过属性io.sort.mb设置。当Map Task的输出结果很多时,就可能会撑爆内存,所以需要在一定条件下将缓冲区中的数据临时写入磁盘,然后重新利用这块缓冲区。这个从内存往磁盘写数据的过程被称为spill,中文可译为溢写。这个溢写是由单独线程来完成,不影响往缓冲区写map结果的线程。溢写线程启动时不应该阻止map的结果输出,所以整个缓冲区有个溢写的比例spill.percent。这个比例默认是0.8(这个比例由属性Io.sort.spill.percent设置),也就是当缓冲区的数据已经达到阈值(buffer size * spill percent = 100MB * 0.8 = 80MB),溢写线程启动,锁定这80MB的内存,执行溢写过程。Map Task的输出结果还可以往剩下的20MB内存中写,互不影响。

当溢写线程启动后,需要对这80MB空间内的key做排序(sort)。排序是MapReduce模型默认的行为,这里的排序也是对序列化的字节做的排序。如果用户作业配置了combiner类,会在排序后进行一次reduce。由于在MapReduce的术语中,reduce只指Reduce端执行从多个Map Task取数据做计算的过程。因此这里的reduce只能叫做combine了。众所周知,Combiner和Reducer可以是同一个类。 

4.每次溢写会在磁盘上生成一个溢写文件,接下来需要将这些溢写文件归并到一起,这个过程就叫做merge。merge的主要做法是针对指定的划分,从各个spill文件中拿出属于同一个划分的所有数据,然后将它们合并在一起,并写入一个已partition和sort的map输出文件中。注意,因为merge是将多个溢写文件合并到一个文件,所以可能也有相同的key存在,在这个过程中如果client设置过Combiner,也会使用Combiner来合并相同的key。

二、Reduce端

简单地说,Reduce Task在执行之前的工作就是不断地拉取当前job里每个Map Task的最终结果,然后对从不同地方拉取过来的数据不断地做merge,也最终形成一个文件作为Reduce Task的输入文件。

1.复制阶段

Map任务完成后,会通知所属TaskTracker状态已更新,TaskTracker进而通知JobTracker,这些通知在心跳机制中进行;Reduce会定期向JobTracker获取Map的输出位置,一旦拿到输出位置,Reduce Task就会从对应的TaskTracker上把数据拉取到本地(如果Map的输出非常小,则会复制到执行reduce任务的TaskTracker的内存中),而不会等待所有的Map任务结束。

2.排序合并

这里的merge如Map端的merge动作,只是数组中存放的是不同Map端copy来的数值。Copy过来的数据会先放入内存缓冲区中,这里的缓冲区大小要比Map端的更为灵活,它基于JVM的heap size设置,因为Shuffle阶段Reducer不运行,所以应该把绝大部分的内存都给Shuffle用。需要强调的是,merge有三种形式:1)内存到内存  2)内存到磁盘  3)磁盘到磁盘。默认情况下第一种形式不启用。当内存中的数据量到达一定阈值,就启动内存到磁盘的merge。与Map 端类似,这也是溢写的过程,这个过程中也可以设置Combiner,然后在磁盘中生成了众多的溢写文件。第二种merge方式一直在运行,直到没有Map端的数据时才结束,然后启动第三种磁盘到磁盘的merge方式生成最终的那个文件。

不断地merge后,最后会生成一个“最终文件”。这个文件可能存在于磁盘上,也可能存在于内存中,默认情况下,这个文件是存放于磁盘中的。

接下来Reduce端就开始进行了reduce操作。

 

posted @ 2014-04-23 23:28  Shanks-香克斯  阅读(251)  评论(0编辑  收藏  举报