作者:Lubin Liu
摘要
MapOutputBuffer作为MapTask的内部类,是MR中二次排序非常重要的一环。本文从基本认识,到详细过程,再到源码级别,由浅入深的介绍了这个类。
第一重:基本认识
1.1 MapOutputBuffer是什么?
比较简单的job可以只有Map阶段,Map的输出直接写入HDFS提供给用户使用。Reduce阶段,往往意味着数据的重新分配,也就是著名的"shuffle",“shuffle”阶段是根据Map阶段
输出的Key,计算Partition,并交给对应Reduce的过程。在进入“shuffle”阶段之前,我们需要对数据进行初步的排序,并按照partion分组,MapOutputBuffer负责这一过程的。
需要注意的是,这个类只有当job中有Reduce逻辑时才会被调用,因为不需要Reduce,也就没有数据的重新分配,也就没有partition的计算,也就没有排序。
1.2 为什么需要MapOutputBuffer?
从类名上我们可以猜到,这是一个Buffer,buffer的作用一般是为了缓存,那问题变成了为什么要作缓存?难道是仅仅为了累积数据加速写入?我认为不全是。Reduce在拉取数据时,是根据partition的。Map的输入顺序和输出顺序是没有关系的,直接输出的数据是完全乱序的,那拉取时就得把Map输出的数据全部扫一遍,只拿自己partition的,这非常的费时费力。所以设计者想到在写出的时候能不能保证局部有序呢,每次缓存一小部分数据,到一定量的时候,对他们按照partition排序,成块的写入到文件里,拉取的时候方便多了,只要看看哪个partition在哪个块。另一个原因是Combiner,我们知道这个模块是为了减少Shuffle数据量,而在map端局部的执行Reduce逻辑。要想达到这种效果,同样需要缓存和排序,将一个partition的小部分数据排到一起,调用一下Combiner再输出。1.3 为什么要理解这个类?
第二重:详细理解运行过程
2.1 过程简介
MapOutputBuffer类里维护了一个环形缓冲区,每个输出的record都以key value的连同partition信息写入到这个缓冲区中,当缓冲区快满的时候,有一个后台的守护进程负责对数据排序,并写入磁盘。2.2 主要变量定义
实现一个带meta信息的缓冲区所需变量,有点多,一下记不住没关系,统一列出为了方便查阅。2. kvmeta:缓冲区的字节视角,并没有占据新的内存,主要方便插入meta信息。每个meta信息由4个int组成,分布如下
一个metadata的长度是METASIZE=16
3. equator:缓冲区的中界点,raw的key value数据和meta信息分别从中界点往两侧填充。
4. kvindex:下次要插入的meta信息的起始位置
5. kvstart:溢写时meta数据的起始位置
6. kvend:溢写时meta数据的结束位置
7. bufindex:raw数据的结束位置
8. bufstart:溢写时raw数据的起始位置
9. bufend:溢写时raw数据的结束位置
10. spillper:当数据占用超过这个比例,会造成溢写,由配置“mapreduce.map.sort.spill.percent”指定,默认值是0.8
11. sortmb:kvbuffer占用的内存总量,单位是M,由配置“mapreduce.task.index.cache.limit.bytes”指定,默认值是100
12. indexCacheMemoryLimit:存放溢写文件信息的缓存大小,由参数“mapreduce.task.index.cache.limit.bytes”指定,单位是byte,默认值是1024*1024(1M)
13. bufferRemaining:buffer剩余空间,字节为单位
14. softLimit:字节单位的溢写阈值,超过之后需要写磁盘,值等于sortmb*spillper