基本框架
MapReduce编程框架大致上可分为map和reduce两个阶段,连接两个阶段的就是shuffle的过程。MapOutputBuffer就是shuffle中非常重要的一个类,它主要用来处理数据的缓存和排序。数据缓存,不用多说,看类名就可以看出来,这个buffer类一定会有累积数据加速写入的功能。至于排序,是因为reduce在后续copy数据时希望数据是有序排列的,这样可以避免再次遍历数据进行排序。
所以,在MapReduce编程框架设计之初,设计者就将map的输出涉及未有序的,即按照partition进行排列,每个partition的内部key再次排序,以减少后续reduce阶段的开销。
运行机制
运行机制我在之前的shuffle分析中有讲过,这里再简单回顾下。
MapOutputBuffer类里维护了一个环形缓存区,缓存区中存储了两种不同类型的数据,即k-v数据和索引数据(kvmeta),两者相邻但互不重叠,以一个临时的分界点equator进行区分。其中k-v数据是map处理之后的kv对数据,kvmeta数据是kv对在环形缓存区中的索引,每个meta信息由4个int组成,包括:value的起始位置、key的起始位置、partition值、value的长度。随着数据不断写入缓存区,但占用的内存达到一定的阈值时,便会触发spill进程将内存缓冲区的数据溢写到本地硬盘中。在spill之前,在缓存区数据还有个SortAndSpill操作,sort先把Kvbuffer中的数据按照partition值和key两个关键字升序排序,移动的只是索引数据,排序结果是Kvmeta中数据按照partition为单位聚集在一起,最终结果是同一partition内的按照key有序排列。具体流程如下:
- equator初始位置为0。在写入数据时,k-v数据[raw data]是向上增长存储的(顺时针),kvmeta数据[meta data]是向下增长的(逆时针)。bufstart(溢写时raw数据的起始位置),bufend(溢写时raw数据的结束位置)和bufindex(raw数据的结束位置)都位于0处。kvstart(溢写时meta数据的起始位置), kvend(溢写时meta数据的结束位置)和kvindex(下次要插入的meta信息的起始位置)沿逆时针方向推进4个int的大小。此外,kvbuffer剩余空间(bufferRemaining)为sortmb(kvbuffer占用的内存总量)*spillper(溢写阈值)。
- 当第一组raw数据写入后,先放入key再放入value,并在bufindex上累加key和value的字节,更新bufindex。再放meta data,从kvindex的基础上,加一个int放value的start的下标,依次放入key start、partition和value length。这样,meta就可以准确定位到刚才放入的raw data。kvindex跳到下一个存放meta data的位置(即kvindex+4),此时,kvstart, kvend, bufstart, bufend的位置不更新。bufferRemaining需减去刚才存放raw data和meta data占用的内存空间。
- 依次存放后续的raw data和meta data。随着数据的不断写入,kvindex不断减少,bufindex不断增加,bufferRemaining也在不断减小。当bufferRemaining空间不足时便会触发spill的线程了。spill的触发可发生在写入meta数据或者raw数据时。
- spill开始前,会先更新kvend和bufend的位置。因为kvindex标示的是下次要插入meta data的位置,所以kvend等于kvindex+4int,kvstart不变,标志第一个meta data写入的位置。bufend是最后raw data中value结束的位置,bufstart标志第一个key开始的位置。kvstart与kvend之间是全部meta data,bufstart与bufend之间是raw data。
- 溢写过程是守护线程完成的,在溢写过程中仍然可以正常写入raw data和meta data。写入需要重新确定equator的位置,该位置位于kvend逆时针到bufend之间,也就是在空余空间的某个位置。由于写入的过程应尽量避免spill的影响,也就是写入的时候要利用当前的空余空间,而不应该侵入已经有数据的空间。kvbuffer中剩余空间的中间位置设为新的分界点,将bufindex指针移动到这个分界点,kvindex指针还是在下一个要插入meta的起始位置(分界点+4),然后两者继续按照既定的轨迹放置数据。注意剩下的可写入空间不应该是所有的空余空间,而是equator到kvend,bufend之间空间比较小的那个。任何一个用完,都代表不能继续写入了。