shuffle过程指的是系统将map task的输出进行排序,并将其传输至reduce task作为输入的过程。这个过程比较复杂,但也是MapReduce的精华所在。
Map阶段
首先,每个map都有一个环形的内存buffer,用来写入map的输出,这个buffer的大小默认是100mb(由mapreduce.task.io.sort.mb设置)。map运行会产生输出,并存入该buffer,当达到设定的阈值(默认0.8或80%,由mapreduce.map.sort.spill.percent设置),后台的一个线程就会开始将buffer内的数据spill(溢出,写入)到磁盘。但spill过程中map的输出还会一直写入到buffer中,直到buffer满了,才会停止,并阻塞map task,直到spill的过程结束。
但在spill线程写入磁盘之前,会首先根据将要执行的reduce task数量,进行分区(partition),并根据key在内存中排序。如果设定了combiner,会对排序的结果进行combine操作。combine操作的目的就是缩小map输出文件的大小。
因此,按照上文所述的过程,每次spill buffer达到溢出的阈值,就会生成一个spill文件,所以map task处理输入结束后,磁盘上可能会有多个spill文件。在map task结束前,会将多个spill文件合并(merge)为一个已分区并排序的大的文件作为map task的输出。同时合并的文件数最多为10个(默认值,由mapreduce.task.io.sort.factor设定)。
在合并过程中,如果spill文件的数量大于3(默认值,由mapreduce. map. combine. minspills设定),还会再执行一次combine操作。
总结一下:map阶段的shuffle过程就是
- map产生输出,存入环形buffer
- buffer达到溢出的阈值
- 对buffer内数据执行partition
- 再执行sort
- 再执行combine(可选)
- 生成一个spill文件,存入磁盘
- 多次达到spill阈值,生成多个spill文件
- 对多个spill文件执行合并(merge)操作,生成一个大的已分区的已排序的输出文件写入磁盘
- 如果spill文件数量大于3,在合并过程的最后执行combine操作。
reduce阶段
copy阶段
首先要注意的是,map输出的文件一定是存在执行map task的节点的本地磁盘上。而reduce task要做的是从集群内所有执行map task的节点处获取它对应的partition的数据。由于map的结束时间不会相同,为了增加效率,reduce task会生成多个线程(默认为5个,由mapreduce.shuffle.parallelcopies设定),每当map task结束就会并行的复制所需的partition数据。
当map task执行成功,会通过心跳机制通知app master(见MapReduce中Job的执行过程),而reduce task会周期的向app master获取map task输出的信息,直到所有map都结束。
这样,多个map的输出就在不同的时间汇聚到reduce所在的节点。而reduce会根据map输出文件的大小将其存储在不同位置,小的话就存在内存,大的话就直接存在硬盘。这个大小的标准就是reduce的input buffer(由mapreduce. reduce. shuffle. input. buffer. percent设定)。
当buffer达到一个阈值(mapreduce. reduce. shuffle. merge. percent)或者buffer内的输出文件数量达到一定数量(mapreduce. reduce. merge. inmem. threshold),buffer中的数据就会先合并、再写入到磁盘。如果指定了combiner,在写入之前会执行combine操作,减少磁盘的写入。
当磁盘写入的数据不断累积,一个后台的线程会将他们合并成更大的已排序的文件。这将会节省后期合并的时间。
sort阶段
当所有的数据复制完毕,reduce进入sort阶段。更确切的说,其实应该叫merger阶段,因为sort的过程在map task已经做过了。
如果需要合并的文件较多,sort的合并会进行若干轮。每轮合并的最多文件数由mapreduce.task.io.sort.factor设定,和map一样。最后的结果就是生成一个单独的已排序的大文件。当然,reduce内部会对这个过程做一些优化,节省执行的时间。
reduce阶段
最后的reduce阶段就很简单了,只需要按行读取sort的输出文件,执行reduce方法即可。reduce的输出会直接存储到目标文件系统,通常是HDFS。
总结一下,Reduce阶段的整个过程:
- 生成多个线程,从各个map节点获取数据
- 如果数据小,就存入内存buffer,大的话,就直接存磁盘
- 当buffer内数据大小或存储的文件数达到阈值,对buffer内的数据执行merge,并存入磁盘。
- 磁盘内的数据不断增多,会有一个后台线程执行merge
- 所有map输出文件复制完毕,copy阶段完毕
- sort阶段将磁盘上所有复制的文件merge成一个大的已排序的文件
- reduce阶段在这个文件上执行reduce方法,并将结果存入hdfs
最后的想法是,shuffle所解决的问题就是在集群内将map产生的输出文件,分区,排序,合并,并提供给reduce使用。但因为集群内的节点数量较多,为了提高传输的效率,在这个过程中做了很多优化,提高了整体的执行效率。