shuffle属于不断被优化和改进的代码库,是MapReduce的“心脏”。
shuffle可以将其定义为:map的输出到reduce的输入(在一些语境中,代表reduce接受map输出的这部分)
map端
我们知道map产生的输出是临时写到本地磁盘的,但是他并不是简单的写到本地磁盘中,这个过程更为复杂,如图:
他会首先使用缓冲的方式写入到内存中,并且处于效率的考虑进行预排序。每个map都有一个缓冲区用于存储任务输出,这个缓冲区的大小默认为100MB,可以通过io.sort.mb属性调整。一旦缓冲的内容达到预设的阀值(通过io.sort.spill.percent,默认是0.8或80%),一个后台进程便把内容溢出(spill)到磁盘。在溢出过程map输出会继续写到缓冲区,如果在此期间被填满,则发生堵塞,直到写磁盘过程完成。这个溢出写的过程会将数据写到mapreduce.cluster.local.dir指定的目录内。在写硬盘之前会根据输出的reduce进行分区(partition),然后对每个分区内容进行排序,如果有combiner函数,则在排序之后执行combiner。每次内存缓冲区达到溢出的阀值,就会新建一个溢出文件(spill file)。最终会有几个溢出文件,这些溢出文件会被合并成一个已分区且已排序的输出文件,io.sort.factor控制一次最多能够合并多少流,默认是10。如果至少有3个溢出文件(这个值由min.num.spills.for.combine属性设置)则就会在输出文件写到磁盘之前在此运行以此combiner。将输出进行压缩可以减少输出及传递到reduce的网络开销,可以设置mapreduce.compress.map.output设置为true,使用mapreduce.map.output.compression.codec指定压缩方法。
reduce端
reduce任务需要集群中若干个map的输出作为其输入,但是每个map的完成时间并不一样,所以只要有一个map输出,reduce就开始复制其输出,这就是reduce端的复制阶段。reduce有少量的复制线程,默认是5个,这个值由mapreduce.reduce.parallel.copies属性改变。
那么reduce如何知道从哪台机器获取map输出呢?
map任务完成后,会通知其父tasktracker,tasktracker会通知jobtracker(在MR2中是applicationMaster),从而jobtracker(applicationMaster)知道了tasktracker与map的映射关系,reduce中的一个线程会定期向applicationMaster(或者jobtracker)进行询问,以便获取map输出的位置。
复制完成后,reduce开始进入排序阶段(其实是合并节阶段,因为排序是在map端进行的),这个阶段合并map输出,保持其排好的顺序。这个合并是循环进行的,可以设置合并因子io.sort.factor,默认是10,即每趟合并10个文件,假设总共50个map,总共进行5趟,最终有5个中文文件。之后是reduce阶段,直接把数据输入到reduce函数,而不用将这5个文件合并称一个大文件。reduce函数输出直接写到HDFS上。
配置调优
map端的调优属性:
属性名称 | 类型 | 默认值 | 说明 |
io.sort.mb | int | 100 | map输出所使用的内存缓冲区大小,以MB为单位 |
io.sort.spill.percent | float | 0.80 | 缓冲区预设的阀值,超过这个百分比开始将内容溢到磁盘 |
io.sort.factory | int | 10 | 排序文件时一次最多合并的流数,在reduce端也是用 |
min.num.spills.for.combine | int | 3 | 运行combiner所需要最少溢出文件数 |
mapreduce.compress.map.output | Boolean | false | 压缩map输出 |
mapreduce.map.output.compression.codec | Class Name | org.apache.hadoop. io.compress.DefaultCodec | 用于map输出的压缩编码器 |
tasktracker.http.threads | int | 40 | 每个tasktracker运行的线程数,用于将map输出到reduce,在YARN不适用 |
这个过程总的来说就是要为shuffle分配更多的内存,但是这时候可能还需要考虑到map函数和reduce函数能够得到足够运行的内。所以一般map函数和reduce函数在编写的时候尽量少占内存。map端可以通过避免多次溢出写磁盘来获得最佳性能,一次是最佳的情况。
reduce端的调优属性
属性名称 | 类型 | 默认值 | 说明 |
mapreduce.reduce.parallel.copies | int | 5 | 用于把map的输出复制到reduce的线程数 |
mapreduce.reduce.copy.backoff | int | 300 | 在声明失败之前,reducer获取一个map输出所花的最大时间,以秒 为单位,如果失败,reducer可以在此时间内尝试重传 |
io.sort.factor | int | 10 | 排序合并时的合并因子 |
mapreduce.job.shuffle.input.buffer.percent | float | 0.70 | 在shuffle阶段,分配给map输出的缓冲区占堆空间的百分比 |
mapreduce.iob.shuffle.merge.percent | float | 0.66 | map输出缓冲区(上面定义的那个)的阀值使用比例,用于启动合并输出和磁盘溢出写的过程 |
mapreduce.inmem.merge.threshold | int | 1000 | 启动合并输出和磁盘溢出写过程的map的输出的阀值数。0或更小,意味着没有阀值限制 |
mapreduce.iob.reduce.input.buffer.percent | float | 0.0 | 在reduce过程,在内存中保存map输出的空间占整个堆空间的比例。reduce阶段开始时 ,内存中的map输出不能大于这个值 |