MapReduce的Shuffle过程

本文详细介绍了MapReduce的Shuffle过程,包括map阶段和reduce阶段的shuffle步骤,如分区、排序、合并、溢写和归并。重点讲解了内存缓冲区、分区器、排序与组合操作,以及如何通过参数调整优化Shuffle性能。

概念

Shuffle中译为“洗牌”,map阶段的输出经过shuffle(分区、排序、合并)后形成一定有规则的数据,按照分区分配给对应的reduce,它描述着数据从map阶段流入reduce阶段的过程。

Shuffle过程

在这里插入图片描述
从图中可以看出,map到reduce之间就是shuflle处理数据的全过程。总共分为两个阶段,map阶段的shuflle和reduce阶段的shuffle,整体流程如下:

1、map结果写入环形内存缓冲区,当内存不足以存储所有数据时,将数据批量溢写到磁盘。为了尽量减少IO消耗,所以在数据写入磁盘之前会先写入缓冲区,待缓冲区达到阈值后才批量将数据写入磁盘
2、partition分区。在数据写入磁盘之前会先进行分区,一个分区对应一个reducer,期望数据在多个reducer之间达到均衡
3、排序(sort)和合并(combine)。数据经过分区之后,先按照key进行排序,如果用户指定了Combiner,再进行combine操作
4、溢写(spill)。经过排序和合并之后的数据会写入磁盘文件,每次spill都会产生一个文件。一个分区上的文件也叫一个segment
5、归并(merge)。一个map最终会生成一个磁盘文件,由于多次spill会产生多个文件,所以需要将这些文件进行merge,最终形成一个有序的大文件。merge过程中有可能遇到相同key的数据,如果用户设置了Combiner,会执行combine操作
以上1-5是map阶段的shuffle,以下是reduce阶段的shuffle步骤
6、拷贝(copy)。当某个map完成后,reduce不断拉取map生成的文件到ruduce。和map阶段一样先将数据写入环形内存缓冲区,当达到阈值时,将数据批量溢写到磁盘
7、排序(sort)和归并(merge)。sort是伴随copy动作时执行的,由于map的输出是有序的,所以copy是进行sort消耗很低。当溢写数据到磁盘之前,如果用户设置了Combiner会先进行combine,然后将数据写入磁盘文件。当接受完map数据会生成多个溢写磁盘文件,将这些文件归并merge,合并成一个有序的大文件

map阶段shuffle详细过程

map阶段shuffle过程就是将map结果进行分区、排序、合并,然后将同一分区数据归并到一起写入磁盘,最终生成一个分区有序的大文件。

写入内存环形缓冲区
map的运算结果会先写入内存缓冲区,当数据量不超过内存大小时,数据就一直在内存中保存,直到所有数据写入完毕,再一次性将数据写入磁盘文件。缓冲区默认100M,由参数mapreduce.task.io.sort.mb控制,当达到默认阈值0.8(80%)时,会触发溢写磁盘动作,阈值由参数mapreduce.map.sort.spill.percent控制
注意:
如果单条记录超过缓冲区大小将直接触发溢写
缓冲区不是阻塞的。当达到阈值进行写入磁盘时,往缓冲区的写入也是正常进行的,写入磁盘完成后,缓冲区达到阈值时会再次进行写入。

Partition
在内存环形缓冲区达到阈值需要写入磁盘之前,会对数据按照key进行分区,每个分区数据对应一个reduce,从而达到数据均衡的目的。
分区可能会导致数据倾斜问题,即某个reduce可能会获取大量数据,某个reduce很少甚至没有数据。因此官方提供了几个分区器Partitioner,有默认的HashPartitioner,也有BinaryPartitioner、KeyFieldBasedPartitioner、TotalOrderPartitioner,用户也可以继承Partitioner基类自定义分区逻辑,通过job.setPartitionerClass()设置不同的Partitioner,避免数据倾斜问题。

内置HashPartitioner代码:

public class HashPartitioner<K, V> extends Partitioner<K, V> {
    public HashPartitioner() {
    }

    public int getPartition(K key, V value, int numReduceTasks) {
        return (key.hashCode() & 2147483647) % numReduceTasks;
    }
}

Sort & Combine
当触发spill动作后,在写入磁盘文件之前按照partition和key进行排序,保证一个partition内的数据有序。如果用户通过job.setCombinerClass()设置了Combiner,则会将相同key的数据执行combine操作
注意:
用户自定义Combiner需要继承Reducer类,重写reduce方法
Combiner虽然会一定程度上将数据合并,减小IO消耗,但是由于相同key的数据合并后,可能会导致出现非预期计算结果

Spill
当发生溢写时启动一个独立的spill线程,负责将数据按照分区逐个写入磁盘文件。写入文件时先按照一个分区数据顺序写入,写完一个分区数据再继续写入下一个分区数据,直到所有分区数据写入完成,最终所有分区数据都按照分区有序的存储在同一个文件中。
一个partition中的数据也叫一个segment,这些分区和数据信息统一存由一个元数据索引维护

Merge
如果map产生的结果数据量很大,会发生多次spill,生成多个磁盘文件,此时就需要将这些文件进行合并,最终生成一个有序的大文件。
合并时候是按照partition一个一个进行合并的,首先从索引中查出这个partition对应的segment,然后归并合并这些文件数据,当一个分区合并完后再执行下一个分区的合并。
mapreduce.task.io.soft.factor参数控制merge过程同时打开的segment数量。默认是10,可按照实际情况调整优化Job

reduce阶段shuffle详细过程

当map彻底完成之后会通知NodeManager,NodeManager会向ApplicationMaster发送心跳汇报信息,reduce从ApplicationMaster获取map完成信息后,向对应的NodeManager发送请求,开始启动reduce的Shuffle,循环拉取map数据(copy),最后将多个文件合并成一个有序的大文件。

Copy
当reduce获知map输入文件所在节点后,建立HTTP连接,不断拉取数据到reduce的内存缓冲区中。reduce阶段shuflle中的内存缓冲区和map阶段的不同,缓冲区内存使用heap size通过参数mapreduce.reduce.shuffle.input.buffer.percent控制,它是相对于mapreduce.reduce.java.opts-参数值得百分比。
mapreduce.reduce.shuffle.parallelcopies参数控制copy的线程数量,用户可根据实际情况进行Job调优。

Sort & Merge
从map读取过来的数据先写入内存缓冲区中,reduce中shuffle的内存缓冲区通常比map阶段的大很多,它使用的heap size。当缓冲区达到一定阈值后,开始内存到磁盘的merge,触发参数由mapreduce.reduce.shuffle.merge.percent控制。sort是伴随copy执行的,由于map的输出结果是有序的,因此在copy时顺便将数据sort。在写入磁盘前,如果用户设置了Combiner会先执行combine操作。当map所有数据都读取到reduce中时,会在生成多个磁盘文件,此时需要将这多个文件合并成一个有序的大文件,叫做磁盘merge。
mapreduce.task.io.soft.factor参数控制文件句柄打开数量,影响merge的速度

当reduce的shuffle执行完成后,才开始真正执行Reducer任务。由于shuffle完成后所有输入都写入了磁盘,所以reducer执行时需要从磁盘拉取数据进行计算。mapreduce.reduce.input.buffer.percent参数控制reducer是否使用内存计算,默认0不使用内存计算。可以通过调整此参数增加reducer计算速度

shuffle调整参数

参数描述默认值
mapreduce.task.io.sort.mbmap阶段缓冲区大小100M
mapreduce.map.sort.spill.percentmap缓冲区溢写到磁盘的触发阈值0.8
mapreduce.task.io.sort.factormerge时打开的文件句柄数,也叫文件流数10
mapreduce.map.output.compress是否开启map压缩false
mapreduce.map.output.compress.codec压缩格式org.apache.hadoop.io.compress.DefaultCodec
mapreduce.reduce.shuffle.parallelcopiesreduce阶段shuffle从map结果copy数据并行度5
mapreduce.reduce.shuffle.input.buffer.percentreduce阶段shuffle的缓冲区大小,相对于mapreduce.reduce.java.opts参数值百分比0.7
mapreduce.reduce.shuffle.merge.percentreduce缓冲区溢写到磁盘的触发阈值0.66
mapreduce.reduce.merge.inmem.thresholds从map读取多少条记录后触发内存到磁盘溢写int类型,0表示禁用
mapreduce.reduce.input.buffer.percentreduce期间用来缓存map结果大小,堆内存百分比。当shuflle结束后,内存中剩余数据必须小于此值才开始reduce计算0.0

水平一般,能力有限,大数据小学生一枚。文章主要用于个人学习和总结,如果能给他人带来帮助,纯属意外。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值