首先,我们先将MR Shuffle的整个流程进行简述:
一.概要:

  • Map端
  • 分区
  • 排序
  • 合并
  • Reduce端
  • 复制
  • 归并
  • reduce
    大概分为五个主要步骤

二.架构图

mapreduce 二次排序 mapreduce shuffle 排序算法_mapreduce

三.详解

Map端

  • 分区 Partition
    首先,为了减少频繁IO的操作,先将数据写入到环形内存缓冲区中,默认大小为100MB,缓冲区中存在一个可设置的阙值(默认为0.8),当阙值达到0.8时,会启动后台线程将数据到磁盘,即缓冲到分区中。
  • 排序 Sort
    排序分为两个部分
  • 在数据从内存缓冲区进入磁盘时,会进行一次排序
  • 在数据分区之后会针对每个分区进行排序,所有在利用MR进行排序的时候,要么指定reduertask为1,要么对数据进行分类,重写Partition类,然后再进行排序
  • spill文件
    每次达到缓存阙值时,将生成一个spill文件作为输出记录,随着map作业的结束而删除,在map作业即将结束之前,spill 文件将会被归并排序为一个索引文件和数据文件。 这是一个多路归并过程。
  • 合并 Combiner
    合并操作相当于是本地的shuffle中的reduce,它在执行map作业的本地节点上运行。但是合并有一个缺点,由于它产生的初衷是当数据量过大时,减小reduce的压力,所以当数据量过小的时候,开启 Combiner占用cpu和内存就是十分不合适的选择,所以有关于Combiner开启条件的设置:min.num.spills.for.combine (默认为3)即spill文件数量大于3,数据量达到启动Combiner要求.

Reduce端

  • 复制阶段 copy
    map输出写入到本地磁盘,而reduce的输出一般不是,而本地磁盘的数据就是reduce的输入,所以reduce的输入数据分布在多个不同的map上,只要完成的maptask达到默认百分比,reduce就开始拷贝它的输出。reduce可以有多个线程进行并行的拷贝输出。educe 任务拥有多个拷贝线程, 可以并行的获取Map 输出。可以通过设定mapred.reduce.parallel.copies 来改变线程数,默认是5。
    如果Map 输出足够小,它们会被拷贝到Reduce TaskTracker 的内存中(缓冲区的大小。由mapred.job.shuffle.input.buffer.percent 控制,指定了用于此目的的堆内存的百分比);如果缓冲区空间不足,会被拷贝到磁盘上。当内存中的缓冲区用量达到一定比例阀值(由mapred.job.shuffle.merge.percent 控制),或者达到了Map 输出的阀值大小(由mapred.inmem.merge.threshold 控制),缓冲区中的数据将会被归并然后spill到磁盘。
    拷贝来的数据叠加在磁盘上,有一个后台线程会将它们归并为更大的排序文件,这样做节省了后期归并的时间。对于经过压缩的Map 输出,系统会自动把它们解压到内存方便对其执行归并。
  • 归并
    将map输出进行归并,生成中间文件。例:若有50个map输出,归并因子为10,则需要归并五次,生成5个中间文件。注意,未必都是平均分配,该举措只是一个优化措施,每趟合并的文件数实际上比示例中展示的更微妙。目标是合并最小数量的文件以便满足最后一趟的合并系数。因此如果是40个文件,我们不会在四趟中,每趟合并10个文件从而得到4个文件。相反,第一趟只合并4个文件,随后三趟合并所有十个文件。在最后一趟中,4个已合并的文件和余下的6个(未合并的)文件合计10个文件。这并没有改变合并的次数,它只是一个优化措施,尽量减少写到磁盘的数据量,因为最后一趟总是直接合并到reduce。
  • reduce
    在Reduce 阶段,Reduce 函数会作用在排序输出的每一个key 上。这个阶段的输出被直接写到输出文件系统,一般是HDFS。在HDFS 中,因为TaskTracker 节点也运行着一个DataNode 进程,所以第一个块备份会直接写到本地磁盘。

四.配置

配置调优
该配置调优方案主要是对以上Shuffle整个过程中涉及到的配置项按流程顺序一一呈现并给以调优建议。
1. Map端
1) io.sort.mb
用于map输出排序的内存缓冲区大小
类型:Int
默认:100mb
备注:如果能估算map输出大小,就可以合理设置该值来尽可能减少溢出写的次数,这对调优很有帮助。

2)io.sort.spill.percent
map输出排序时的spill阀值(即使用比例达到该值时,将缓冲区中的内容spill 到磁盘)
类型:float
默认:0.80

3)io.sort.factor
归并因子(归并时的最多合并的流数),map、reduce阶段都要用到
类型:Int
默认:10
备注:将此值增加到100是很常见的。

4)min.num.spills.for.combine
运行combiner所需的最少溢出写文件数(如果已指定combiner)
类型:Int
默认:3

5)mapred.compress.map.output
map输出是否压缩
类型:Boolean
默认:false
备注:如果map输出的数据量非常大,那么在写入磁盘时压缩数据往往是个很好的主意,因为这样会让写磁盘的速度更快,节约磁盘空间,并且减少传给reducer的数据量。

6)mapred.map.output.compression.codec
用于map输出的压缩编解码器
类型:Classname
默认:org.apache.Hadoop.io.compress.DefaultCodec
备注:推荐使用LZO压缩。Intel内部测试表明,相比未压缩,使用LZO压缩的 TeraSort作业,运行时间减少60%,且明显快于Zlib压缩。

7) tasktracker.http.threads
每个tasktracker的工作线程数,用于将map输出到reducer。
(注:这是集群范围的设置,不能由单个作业设置)
类型:Int
默认:40
备注:tasktracker开http服务的线程数。用于reduce拉取map输出数据,大集群可以将其设为40~50。

  1. reduce端
    1)mapred.reduce.slowstart.completed.maps
    调用reduce之前,map必须完成的最少比例
    类型:float
    默认:0.05

2)mapred.reduce.parallel.copies
reducer在copy阶段同时从mapper上拉取的文件数
类型:int
默认:5

3)mapred.job.shuffle.input.buffer.percent
在shuffle的复制阶段,分配给map输出的缓冲区占堆空间的百分比
类型:float
默认:0.70

4)mapred.job.shuffle.merge.percent
map输出缓冲区(由mapred.job.shuffle.input.buffer.percent定义)使用比例阀值,当达到此阀值,缓冲区中的数据将会被归并然后spill 到磁盘。
类型:float
默认:0.66

5)mapred.inmem.merge.threshold
map输出缓冲区中文件数
类型:int
默认:1000
备注:0或小于0的数意味着没有阀值限制,溢出写将有mapred.job.shuffle.merge.percent单独控制。

6)mapred.job.reduce.input.buffer.percent
在reduce过程中,在内存中保存map输出的空间占整个堆空间的比例。
类型:float
默认:0.0
备注:reduce阶段开始时,内存中的map输出大小不能大于该值。默认情况下,在reduce任务开始之前,所有的map输出都合并到磁盘上,以便为reducer提供尽可能多的内存。然而,如果reducer需要的内存较少,则可以增加此值来最小化访问磁盘的次数,以提高reduce性能。

3.性能调优补充
相对于大批量的小文件,hadoop更合适处理少量的大文件。一个原因是FileInputFormat生成的InputSplit是一个文件或该文件的一部分。如果文件很小,并且文件数量很多,那么每次map任务只处理很少的输入数据,每次map操作都会造成额外的开销。