Bootstrap

MapReduce执行流程

执行流程
MapTask执行流程
Read:读取阶段

MapTask会调用InputFormat中的getSplits方法来对文件进行切片

切片之后,针对每一个Split,产生一个RecordReader流用于读取数据

数据是以Key-Value形式来产生,交给map方法来处理。每一个键值对触发调用一次map方法

Map:映射阶段

map方法在获取到键值对之后,按照要求对键值对中的数据进行拆分解析,解析之后按照要求输出键值对形式的结果

Collect:收集阶段

MapTask拆分产生数据之后,并不是直接将数据传输给ReduceTask,而是会调用OutputCollector.collect方法来收集输出结果

OutputCollector.collect在收集到数据之后,会先按照指定的规则,对数据进行分区,分区完成之后,会将数据写到缓冲区中

缓冲区本质上是一个环形的字节数组,默认大小是100M(可以通过属性mapreduce.task.io.sort.mb来调节),默认阈值是0.8(可以通过属性mapreduce.map.sort.spill.percent来调节)

Spill:溢写阶段

当缓冲区使用达到指定阈值的时候,MapTask会将缓冲区中的数据冲刷(flush)到磁盘上,这个过程称之为溢写(spill)

在溢写的时候,会按照如下步骤进行

排序(sort)。此时,是将毫无规律的数据整理成有序数据,采用的Quick Sort(快速排序)。需要注意的是,数据在排序的时候,是按照分区内进行的排序,即先按照分区大小进行分区号的升序,然后每一个分区内按照指定规则排序。因此,数据是分区内有序

合并(combine)。如果用户指定了Combiner,那么此时会将数据进行合并处理

写出(flush)。按照分区的顺序,将每一个分区中的数据依次写入任务的工作目录的临时文件output/spillN.out中。N表示溢写次数。溢写次数不能完全由原始数据大小来决定,还得考虑map方法的处理过程。此时,单个结果文件中是分区且有序的,整体而言是局部有序

压缩(compress)。如果用户指定了对MapTask的结果进行压缩,那么数据在写出之后还会进行压缩处理

记录(record)。将分区数据的元信息记录到内存索引数据结果SpillRecord中。元信息中包含:每一个分区在每一个临时结果文件中的偏移量(offset),每一个分区压缩前的数据大小以及压缩后的数据大小。如果SpillRecord中记录的所有的元信息大小之和不超过1M,那么SpillRecord中的数据也会写到output/spillN.out.index中

Merge:合并阶段

当MapTask将所有的数据处理完成之后,会将所有的临时结果文件spillN.out合并(merge)成一个大的结果文件output/file.out,同时会为这个文件生成索引文件file.out.index

在merge过程中,数据会再次进行分区。分区之后数据会再次进行排序。注意,此次排序是将局部有序的数据整理成整体有序,采用的是Merge Sort(归并排序)

在merge过程中,如果指定了Combiner,那么数据会进行combine操作

merge的时候,默认情况下,是每10个(可以通过属性mapreduce.task.io.sort.factor来调节)小文件合并成1个大文件,通过多次合并,最后会产生一个结果文件file.out。这样子,能够有效的避免同时打开大量文件带来的开销

注意:无论是否会进行Spill过程,最后都会产生一个file.out文件!!!

ReduceTask执行流程
当达到启动阈值的时候,ReduceTask就会启动。默认情况下,启动阈值是0.05,即5%的MapTask结束ReduceTask就会启动。可以通过属性mapreduce.job.reduce.slowstart.completedmaps来调节

ReduceTask启动之后,会启动fetch线程来抓取数据。默认情况下,每一个ReduceTask最多可以启动5个fetch线程来抓取数据。可以通过属性mapreduce.reduce.shuffle.parrellelcopies来调节

fetch启动之后,会通过http请求的get请求来获取数据,在请求的时候,会携带参数表示当前的分区号。MapTask在收到请求之后,根据参数(分区号)来解析file.out.index文件,从中获取指定分区的位置,然后才会读取file.out,将对应分区的数据返回

fetch线程在抓取到数据之后,会先对数据进行大小判断。如果数据超过了ReduceTask的缓冲区阈值,那么会将数据直接以文件形式写到磁盘上;如果没有超过ReduceTask的缓冲区阈值,那么就先放到缓冲区中

缓冲区的大小由属性mapreduce.reduce.shuffle.input.buffer.percent来决定,默认是0.7,是ReduceTask执行过程中允许占用内存的70%

缓冲区的阈值由属性mapreduce.reduce.shuffle.merge.percent来决定,默认是0.66,即缓冲区大小的66%

fetch线程在抓取数据的同时,ReduceTask会启动两个后台线程将抓取来的数据进行merge,以防内存以及磁盘占用过多

所有的fetch线程完成之后,ReduceTask会将抓取来的所有的数据进行排序。同样,此时也是将局部有序的数据整理成整体有序的数据,所以依然采用的是归并排序(Merge Sort)

经过排序和merge,最终产生了一个大的临时文件来交给ReduceTask来处理。此时,ReduceTask会将相同的键对应的值放到一起,形成一个伪迭代器(本质上就是一个流,来读取刚刚产生的临时文件),这个过程称之为分组(group)。也正因为是伪迭代器(将流包装成了迭代器,临时文件读取完之后就会被销毁),所以只能遍历一次!

分组完成之后,每一个键调用一次reduce方法,按照指定的规则来对数据进行聚合

reduce处理完之后,会将结果传递给OutputFormat,按照指定规则将数据以指定形式写出到指定位置
常见优化方案
减少MapTask的溢写次数。溢写是将数据写到磁盘上,程序和磁盘交互次数越多,效率越低;此时,如果需要提高效率,就可以考虑减少MapTask和磁盘的交互次数

调节缓冲区的大小。通过mapreduce.task.io.sort.mb来调节,实际过程中,一般会将这个值调节为250M~400M

调节缓冲区阈值的大小。通过属性mapreduce.map.sort.spill.percent来调节

减少Merge次数。通过属性mapreduce.task.io.sort.factor来调节

增加Combiner。如果计算可以传递,那么建议在程序中使用Combiner。根据经验,使用Combiner,大约可以提升40%的效率

减少Reduce。如果MapTask处理完成之后,不需要使用Reduce聚合,那么此时可以直接省略Reduce

合理设置ReduceTask的执行内存。默认情况下,每一个ReduceTask最多占用1G内存,如果试图超过1G内存,就会被kill掉

调大ReducedTask的执行内存。通过mapreduce.reduce.memory.mb属性来调节,单位是MB,默认值是1024

调大缓冲区的占比。通过mapreduce.reduce.shuffle.input.buffer.percent属性来调节

调大缓冲区的阈值。通过mapreduce.reduce.shuffle.merge.percent属性来调节

增加fetch线程的数量。通过mapreduce.reduce.shuffle.parrellelcopies属性来调节

可以考虑对数据进行压缩。即将MapTask产生的结果进行压缩之后传递给ReduceTask,但是这种方案是在网络和解压效率之间进行了平衡/对比

其他
小文件问题
在大数据开发环境中,虽然实际处理的文件大部分都是大文件,但是依然无法避免产生小文件

一般而言,如果文件大小≤Block*0.8,那么此时就认为这是一个小文件。实际过程中,一般认为不超过100M的文件就是小文件

小文件在分布式环境下的问题

存储:在HDFS中,每一个小文件对应一条元数据,如果存储大量的小文件,那么会产生大量的元数据,此时会导致占用较多的内存,同时导致元数据的读写效率降低

计算:在MapReduce中,每一个小文件对应一个切片,每一个切片对应一个MapTask。如果需要对大量的小文件进行处理,那么就意味着需要产生大量的MapTask,导致集群的资源被同时大量的占用和释放

目前对小文件的处理方案无非两种:合并(merge)和打包

MapReduce提供了一种原生的打包方案:Hadoop Archive,将多个小文件打成一个.har包

# 将/txt/打成txt.har包,放到/result下
hadoop archive -archiveName txt.har -p /txt /result
数据倾斜
在集群中,因为处理的数据量不均等导致任务执行时间不一致而产生的等待,称之为数据倾斜

数据倾斜可能发生在Map端,也可能会发生在Reduce端

Map端产生数据倾斜的直接原因:需要同时处理多个文件,且文件大小不均等,文件不可切

Reduce产生数据倾斜的直接原因:对数据进行分类

数据倾斜无法避免,因为数据本身就有倾斜特性

理论上来说,数据倾斜产生之后可以解决,但是实际过程中,不太好解决;对于Reduce端的数据倾斜,实际过程中,可能会采用二阶段聚合方式来处理

推测执行机制
推测执行机制本质是MapReduce针对慢任务的一种优化。当出现慢任务的时候,MapReduce会将这个任务拷贝一份到其他节点上,两个节点同时执行相同的任务,谁先执行完谁的结果就作为最终结果,另一个没有执行完的就会被kill掉

慢任务的出现场景

任务分配不均匀:每一个节点被分配到的任务数量不同

节点性能不一致:每一台服务器的配置不同

数据倾斜:任务之间处理的数据量不均等

实际工作中,因为数据倾斜而导致的慢任务出现的概率更高,此时推测执行机制除了会占用更多的集群资源以外,并不能提高执行效率。因此,实际过程中,一般会考虑关闭推测执行机制

<!-- MapTask的推测执行机制 -->
<property>
    <name>mapreduce.map.speculative</name>
    <value>true</value>
</property>
<!-- ReduceTask的推测执行机制 -->
<property>
    <name>mapreduce.reduce.speculative</name>
    <value>true</value>
</property>
join
案例:统计每一天的利润(文件:order.txt和product.txt)

需要同时处理多个文件,且信息来源不是只依靠单个文件就能处理,此时需要确定一个主文件,将其他的文件作为缓存文件进行处理,这个过程称之为join

;