首先强调一下,本文是以hadoop 1.x版本为基础整理完成的。
在hadoop中,每个MapReduce任务都被初始化成为一个Job,每一个Job都可以分成两个阶段:Map阶段以及Reduce阶段。Map阶段会接受一个<key,value>的输入,产生一个<key,value>的中间输出,hadoop会把这些key相同的value集合到一起传给reduce函数,reduce会接受一个<key,(list of values)>的输入,处理后输出一个<key,value>。
这里,我们从HDFS中数据的block块开始介绍,数据如何被处理,直到被Reducer Task输出。由上图可知,数据一般处理流程是 mapper task -> combiner -> shuffle -> reduce task。
首先要介绍一下FileInputFormat类:
FileInputFormat是所有以文件作为数据源的InputFormat实现的基类,FileInputFormat保存作为job输入的所有文件,并实现了对输入文件计算splits的方法。至于获得记录的方法是有不同的子类——TextInputFormat进行实现的。
这是它的两个方法。
有三个作用:
1.验证作业的输入是否规范.
2.把输入文件切分成InputSplit
3.提供RecordReader 的实现类,把InputSplit读到Mapper中进行处理.
在执行mapreduce之前,原始数据被分割成若干split,每个split作为一个map任务的输入,在map执行过程中split会被分解成一个个记录(key-value对),map会依次处理每一个记录。 FileInputFormat只划分比HDFS block大的文件,所以FileInputFormat划分的结果是这个文件或者是这个文件中的一部分. 如果一个文件的大小比block小,将不会被划分,这也是Hadoop处理大文件的效率要比处理很多小文件的效率高的原因。
例如:一个1G的文件,会被划分成16个64MB的split,并分配16个map任务处理,而10000个100kb的文件会被10000个map任务处理。
我们需要重点介绍一下TextInputformat类,TextInputformat类是默认的处理类,处理普通文本文件。一般main函数中我们在job.setInputformatClass([Inputformat class])时,[Inputformat class]一般都是TextInputformat.class,它的特点如下:
1.文件中每一行作为一个记录,他将每一行在文件中的起始偏移量作为key,每一行的内容作为value。
2. 默认以\n或回车键作为一行记录。
3. TextInputFormat继承了FileInputFormat。
除了TextInputformat类,还有其他输入类,如CombineFileInputFormat,相对于大量的小文件来说,hadoop更合适处理少量的大文件,CombineFileInputFormat可以缓解这个问题,它是针对小文件而设计的。KeyValueTextInputFormat:是当输入数据的每一行是两列,并用tab分离的形式的时候,KeyValueTextInputformat处理这种格式的文件非常适合。NLineInputformat:NLineInputformat可以控制在每个split中数据的行数。SequenceFileInputformat:当输入文件格式是sequencefile的时候,要使用SequenceFileInputformat作为输入。这些都不重点介绍,需要的话可以具体查询这些类的使用。
数据经过FileInputFormat处理后,将<key,value>格式的数据交给Mapper Task处理后,再输出输出以<key,value>格式的数据,而每一个map可能会产生大量的输出,combiner的作用就是在map端对输出先做一次合并,以减少传输到reducer的数据量。
接下来介绍一下combiner过程。
combiner最基本是实现本地key的归并,combiner具有类似本地的reduce功能。如果不用combiner,那么,所有的结果都是reduce完成,效率会相对低下。使用combiner,先完成的map会在本地聚合,提升速度。
注意:Combiner的输出是Reducer的输入,如果Combiner是可插拔的,添加Combiner绝不能改变最终的计算结果。所以Combiner只应该用于那种Reduce的输入key/value与输出key/value类型完全一致,且不影响最终结果的场景。比如累加,最大值等。
这里,对combiner进行一个小的总结:
Map的输出结果是由collector处理的,所以Map端的shuffle过程包含在collect函数对Map的输出结果处理过程中。
每个map有一个环形内存缓冲区,用于存储任务的输出。默认大小100MB(io.sort.mb属性),一旦达到阀值0.8(io.sort.spill.percent),一个后台线程把内容写到(这就是spill)磁盘的指定目录(mapred.local.dir)下的新建的一个溢出写文件。写磁盘前,要partition,sort。如果有combiner,combine sort后数据再写磁盘。等最后记录写完,合并全部溢出写文件为一个分区且排序的文件。
Reduce端,shuffle可以分成三个阶段:复制Map输出,合并排序和Reduce处理。
Reducer通过Http方式得到输出文件的分区。TaskTracker为分区文件运行Reduce任务。复制阶段把Map输出复制到Reducer的内存或磁盘。一个Map任务完成,Reduce就开始复制输出。排序阶段合并map输出。然后走Reduce阶段。