MapReduce(十): 详解Map任务运行

来源:互联网 发布:学英语口语软件下载 编辑:程序博客网 时间:2024/06/08 06:49

在执行map任务时首先对该任务处理的文件段进行读入,在处理完毕后,该Job中没有设置Reduce任务,则直接把该任务所处理文件段数据的结果写入到HDFS中该Job的output目录下,output目录由用户在任务提交前通过参数mapred.output.dir指定。

如果该Job中存在Reduce任务,则把结果key默认按照reduce任务个数进行hash方式取余确定放在哪个分区中,到Reduce阶段,Reduce任务取属于该任务分区的数据进行reduce操作。

如果Map阶段有指定的combiner,则对map的结果做combine操作,combine通常与Reduce操作相同,对map的结果先做一次reduce,会减少map向reduce传输数据量。

 

Map对最终写入的数据需要对相同分区的数据放在一起,并且相同分区的数据需要按照key的顺序排放,其过程如下:

1.   先把key、value数据放在划分的固定大小的内存中

2.   等到该内存达到使用的阈值时,把内存中的数据按照分区,key的顺序存到文件中,并记录该文件中各个分区数据起始位置的索引值。

3.   在从内存输出到文件时,如果存在combine,则对内存中数据先做一次combine。

4.   重复循环第二,第三步,最终把所有结果存放在不同的文件中(每次从内存转到文件时输出为一个独立文件)。

5.   然后对不同文件的结果进行分区的合并,并且对同一分区的数据进行重新排序,最终结果输出到一个文件中,并记录该文件中各个分区数据起始位置的索引值。

 

内存中存放key,value的数据结构:

 

在内存中一对key和value就是一条记录,kvoffsets[]数组的长度就是能存放的最大记录个数,存放的是某条记录在kvbuffer[]的信息,如上图中kvoffsets数组下标3中存放的数据就是第三条记录在kvindices数组的开始位置,固定占用3个int长度,kvoffsets[]是一个int数组;kvindices[]数组存放的是某一条记录的属于哪个分区,以及这条记录在kvbuffer中存放key值和value值的开始位置,kvindices[]是一个int数组;kvbuffer[]是一个byte数组,它存放的是具体key和value的数据。数组的数据是循环存储,数组中标识了数据段的开始位置,结束位置,以及当前的写入位置,每次把缓存中的数据写到文件后,开始位置移到结束位置,从开始位置后再开始写,写到尾后再从头开始写,但不超过当前的开始位置。

因此想获得某一条记录的key值和value值,首先通过kvoffsets[]获取该条记录在kvbuffer[]中key和value的起始位置,kvbuffer[]中存放key数据的长度,就是该条记录的value的起始位置-key的起始位置,kvbuffer[]中存放value数据长度,就是下一条记录key的起始位置-该条记录value的起始位置。

 

当kvoffsets[]或kvbuffer[]中数组使用达到阈值上限时,就触发把内存中的数据输出到文件中。在对内存中的数据输入到文件时,先进行排序,排序的结果是把相同分区的数据放一起,相同分区的数据按照key的顺序存放。排序的结果不对kvindices[]和kvbuffer[]内存中的数据做变化,只对kvoffsets[]的数据做变化,如下图从虚线变化到实线,把原来数组下标3中指向kvindices[]数据段的值与下标4的值做了交换。

 


 每一个输出文件都对应一个该文件的索引记录,记录各个分区在这个文件中的起始位置以及在文件中的原始数据长度和压缩后数据长度(如果指定压缩方式的话,输出到文件进行压缩存放)。 

Map后的结果全都记录在文件中,map完成后对所有文件进行合并输出到一个文件中,合并到一个文件时需要对所有文件的结果进行排序和combine,涉及到数据比较多,不可能把所有数据读到内存进行排序,源代码采用边从各个文件读取边排序的方式,避免了内存的使用。

 

如上图,每个文件都对应一个索引记录,指明各个分区在文件中的位置以及分区长度,在合并时按照分区顺序,取出该分区在所有文件中的索引记录,根据索引找到该分区在每一个文件中第一个key的值,然后按照key值的顺序排放文件读取的顺序,如下图取出所有分区3的数据,然后按照该分区第一个key的值顺序排放

 

每个分区的数据都是按照key顺序排放,文件2分区3的数据包含Key1,Key6,Key8数据;文件3分区3的数据包含Key1,Key3,Key7;文件1分区3的数据包含Key4,Key5,Key9。这里文件2分区3和文件3分区3拥有相同Key1的数据。

找到分区3在所有文件中的位置并排好序后,打开该文件的句柄开始顺序读取数据,每次读完一个Key的数据后,就重新对各个文件中第一个Key进行排序,调整文件读取的顺序,由于原先分区的数据都已经按照Key顺序存储,因此能够保证所有文件的读取都是按照Key的顺序读取。

在读取所有分区的数据并输出到最终文件同时记录在这个文件中各个分区的索引值,并把最终的索引记录输出到文件中,供Reduce任务获取某个分区的数据用。 最终输出到文件的格式与从内容输出到文件的格式相同。

 

代码分析:

上层调用cellect方法,把结果数据Key,Value写入到缓冲中,如果缓冲数据达到上限,则把数据写入到文件中,每次把缓存中的数据写入到一个独立文件中:

MapTask.MapOutputBuffer.java#collect()

 

把数据写入到缓冲后保存该记录在缓存中的索引位置:

MapTask.MapOutputBuffer.java#collect()

 

写入缓存的处理,判断缓存数据是否到达上限,如到达了则先写入到文件中:

MapTask.BlockingBuffer.java#write()

 

把内存中的数据输出到文件中处理:

MapTask.SpillThread.java#sortAndSpill()



缓存中的数据是循环存储,从头存到尾,然后再从头开始,因此取数据时需要判断是否跨尾部和头部:

 

所有数据都输出到文件中,然后对所有文件进行合并处理:

MapTask.MapOutputBuffer.java#mergeParts()


0 0
原创粉丝点击