MapReduce之collect过程分析

来源:互联网 发布:c语言的未来 编辑:程序博客网 时间:2024/06/04 01:09

我们知道map任务在处理完一个key/value之后,就会进行collect操作:分区,排序,溢写等操作,这些操作都属于collect操作。

 

我们在map方法中,处理完一对key:value,就会调用write方法,本质上就是调用MapTask初始化的NewOutputCollector的write方法, 进而调用collect方法,开始collect流程

 

一 计算key落在哪一个分区

 

再调用collect方法的时候,会传入当前的key 和 value。并且传入当前key所在的分区. 那么这里分区的算法是什么呢?

#如果没有设置reduce任务的数量,那么默认就一个reduce 任务。

那么分区无论多少key:value对都只在一个分区0上。

#如果reduce任务数量大于 1,那么就会用用户在job中设置的Partitioner来计算分区,如果没有设置就使用默认的分区HashPartitioner

#默认HashPartitioner的算法就是(key.hashcode& Integer.MAX_VALUE) % reduce 任务数 = 当前key位于哪一个分区

 

 

 

 

二 环形缓冲区介绍

 

最后通过调用MapOutputBuffer#collect方法,正式进入collect流程:

MapOutputBuffer使用一个内存缓冲区,将结果暂时存储在这里。当缓冲区达到一定阀值比例,默认80%的时候,再将缓冲区数据写入磁盘。

 

一般的数据缓冲区有多种实现,常见的有:

=>单向缓冲区:生产者向缓冲区写数据,写满后一次性写到磁盘,但是存在性能不高的情况,因为不能支持同时读写数据。即所谓我写你就不能读,写满了你才能读。读完了你才能继续写。

=>双缓冲区:一个用于写数据,一个用户读,比单向稍微提高一点新能,只能在一定程度支持并发读写。即所谓我先写第一个,写满之后,开始读取,然后新来的请求开始写第二个;第二个写满后,第一个已经读取完后,有可以在第一个上面写

=>环形缓冲区:当缓冲区使用达到阀值,便开始向磁盘写数据,同时生产者仍然可以不断向剩余空间写数据,进而真正到达读写并行。


MapOutputBuffer通过SpillThread线程将环形缓冲区达到80%的时候,就将数据写入临时文件,等到所有数据处理完毕,对所有临时文件进行一次合并生成一个最终文件



首先我们需要理解几个概念:

缓冲区kvbuffer: 其实是一个字节数组,初始容量是指定的每一个map任务的内存数量>> 20

kvmeta:根据kvbuffer字节数组构造的一个IntBuffer。它主要是用于存储key-value的索引信息,包括key开始位置,value开始位置,partition信息,以及value长度

kvmeta同样是存储在kvbuffer里的,也就是说kvbuffer除了要存储真正的key-value数据,还要存储其索引信息,所以kvbuffer这个缓冲区可以理解为它包含两个部分:数据区和索引区

 

数据区和索引区是相邻的但是不重复的两个区间,有一个分界点equator(赤道)来分割,初始equator的位置是0。数据存储方向是向上增长,索引存储方向是向下增长

 

kvstart:索引记录开始的位置

kvindex:下一个可以写索引记录的位置,类似于buffer中的position

kvend:索引记录结束的位置

在开始的时候kvstart=kvend=kvindex

kvend在spill的时候 kvend=kvindex, spill结束,就会kvend=kvstart

 

bufstart:写入key-value的开始位置

bufend:写入key-value的结束位置

bufindex:下一个要写入key-value的位置

bufvoid:缓冲区结束位置,默认和缓冲区长度一致。写到这儿,表示不能继续读写

 

数据区和索引取都始终闷着头一直跑,那么钟会有碰头的那一天,碰头之后,再重新开始或者移动内存就比较麻烦了。所以在spill结束的时候调用resetSpill方法,将kvbuffer中剩余的空间的中间位置设置为新的分界点equator。



我们知道当kvbuffer达到了使用阀值,我们就需要开始进行spill操作,spill操作是由一个线程类SpillThread来完成的,它会调用sortAndSpill方法:

1先计算文件大小

2获取写入本地文件的名字,比如output/spill0.out

3使用快速排序对缓冲区kvbuffer中间[bufstart,bufend)之间的数据排序:

#现根据partition升序排序

#然后再对key进行排序

4构建IFile.write,传入输出流,输出到指定文件中,在这里支持压缩

5如果用户设置了combiner,在这里可能会启用combiner,则对每一个分区中的数据进行一次聚合操作

6将元数据写入SpillRecord,如果内存中索引大于1M,则溢写到out

Put/spillN.out.index文件中

7spill结束调用reset方法,重新设置spill



原创粉丝点击