作为一个hadoop的初学者,我将在本文中尽量分析MapReduce执行流程,如果有什么不对的地方,还请各位大神不吝赐教。
1、InputFormat
Client端提交任务的数据和配置到服务器层,在进入MapReduce执行之前,因为文件数据已经被HDFS切成了固定大小的128M的文件块,所以很可能出现断句不准确的情况。
所以,要先经过InputFormat,默认为TextInputFormat类,TextInputFormat继承了FileInputFormat类,FileInputFormat实现了InputFormat接口,InputFormat接口有以下的两个方法:
public interface InputFormat<K, V> {
//获取由输入文件计算出输入切片(InputSplit),一个切片就是一个Map Task,解决数据或文件分割成片问题。
InputSplit[] getSplits(JobConf var1, int var2) throws IOException;
//创建RecordReader,从InputSplit中读取数据,解决读取分片中数据问题,形成KV键值对。
RecordReader<K, V> getRecordReader(InputSplit var1, JobConf var2, Reporter var3) throws IOException;
}
通过getSplits方法获取的切片(InputSplit)是逻辑分片,默认大小是128M,但每一个InputSplit比128M有可能大,也有可能小,也有可能等。因为逻辑分片要保证文件块的完整正确,所以要通过认为规定的换行标志来分割。如果一个文件在切分到最后一个InputFormat时发现只剩下不超过128M的1.1倍,那么就不在切分了。
而且,因为hadoop采用的是hdfs的分布式存储方式,导致输入的文件不一定存在一台机器上,所以逻辑分片中获取的不仅仅是文件块内部的分割信息,还要获取文件块的路径。
通过InputFormat,Mapreduce框架可以做到:
- 验证作业输入的正确性。
- 将输入文件切割成逻辑切片(InputSplit),一个InputSplit将会被分配给一个独立的MapTask,一个切片就是一个Map Task。
- 提供RecordReader实现,读取InputSplit中的“K-V对”供Mapper使用,其中的”K”是每一行的偏移量,”V”是每一行的值。
2、Map Task
通过InputFormat得到“K-V对”输入Mapper,其中的”K”是每一行的偏移量,”V”是每一行的值。
然后根据Mapper的业务逻辑所写的map()函数,对每个”K-V对”处理,产生新的”K-V对”,然后通过OutPutCollector输出到一个环形缓冲区,然后再通过环形缓冲区触发spill溢出。这里输入和输出的KV键值对都是可序列化的。
环形缓冲区:
这里使用环形缓冲区溢出,环形缓冲区可使得内存使用最优,性能较好。是因为每次从头到尾输入进环形缓冲区之后,因为头尾相接,一旦满了之后触发spill溢出,把环形缓冲区中的数据溢出之后就清除,然后再把新的数据输入进来。而又为了防止数组越界,每次数据输入到环形缓冲区的80%时,就会溢出。
环形缓冲区示意图:
3、Shuffle
shuffle是MapReduce处理流程中的一个过程,分散在各个map task和reduce task节点上完成,总体看来分为三步:
1)HashPartition分区
把经过环形缓冲区输出的KV键值对,通过hash分区方式进行有效分区,使用hash分区是为保证数据能够有效地分散在不同的reduce task节点。
2)排序
可以根据业务逻辑排序,也可以进行默认排序。
3)Combiner
使用combiner的归并方法进行分区合并。
经过这三步,map task的输出会根据分区输入到各个reduce task中。
4、Reduce Task
Reduce task 会取到同一个分区的来自不同 maptask 的结果文件,reducetask 会将这些文件再进行合并(归并排序)。
合并成大文件后,shuffle 的过程也就结束了,后面进入 reduce task 的逻辑运算过程。
然后调用GroupingComparator对大文件里面的数据进行分组,从文件每取出一组”K-V对”,调用用户自定义的reduce()方法进行逻辑处理。
最后通过OutputFormat方法将结果写到part-r-000**文件中,存储在HDFS上。
最后分享一张完整的流程图: