基于2.7.1源码进行的分析
map端的执行执行的主要过程:
首先会对block进行split,每个split上启动一个map task,map方法执行完之后,最终会把输出写到磁盘上。如果没有热的侧阶段,则直接输出到hdfs上,如果有有reduce作业,则每个map方法的输出在写磁盘前线在内存中缓存。每个map task都有一个环状的内存缓冲区,存储着map的输出结果,在每次当缓冲区快满(默认是达到80%)的时候由一个独立的线程spillThread将缓冲区的数据以一个溢出文件的方式存放到磁盘,当整个map task结束后再对磁盘中这个map task产生的所有溢出文件做合并,被合并成已分区且已排序的输出文件。然后等待reduce task来拉数据。
执行的大致流程:
1、执行run方法-->runJobSetupTask-->runNewMapper-->input.initialize(split, mapperContext)-->mapper.run(mapperContext)
2、重复调用map()方法,执行context.write(key,value)-->TaskInputOutputContext中的write()-->TaskInputOutputContextImpl中的write()-->NewOutputCollector中的write()方法,write()方法调用collector.collect()-->MapOutputCollector中的collect()-->MapOutputBuffer(MapOutputCollector的实现类)中的collect()。
3、MapOutputBuffer的collect方法中把key和value序列化后存储在一个环形缓存中,如果缓存满了则会调用startspill方法
设置信号量,使得一个独立的线程SpillThread可以对缓存中的数据进行处理。
4、SpillThread线程的run方法中调用sortAndSpill方法对缓存中的数据进行排序后写溢出文件。
5、当map输出完成后,会调用output的close方法。
6、在close方法中需要对缓冲区做一些最后的清理,调用MapOutputBuffer类中flush方法,合并spill{n}文件产生最后的输出。先等待可能的spill过程完成,然后判断缓冲区是否为空,如果不是,则调用sortAndSpill,做最后的spill,然后结束spill线程,然后清空kvbuffer,最后调用mergeParts()。
--->sortAndSpill():先对键值进行排序,如果没有combinerRunner直接溢写,否则先进行combine然后再溢写。
--->mergeParts():merge是将多个溢写文件合并到一个文件,所以可能也有相同的key存在,在这个过程中如果配置设置过Combiner,也会使用Combiner来合并相同的key。?mapreduce让每个map只输出一个文件,并且为这个文件提供一个索引文件,以记录每个reduce对应数据的偏移量。