MapReduce Shuffle 原理以及优化
1、MapReduce 编程模型
Hadoop MapReduce 是一个分布式计算框架,用于编写批处理应用程序;
数据以一条记录为单位经过 map
方法映射成KV,相同的key为一组,这一组数据调用一次 reduce
方法,在方法内迭代计算着一组数据。
2、MapReduce 的实现原理
Client 提交
客户端在进行submit() 之前,会获取待处理数据的信息,然后做出执行前的规划;很重要,支撑了计算向数据移动和计算的并行度;
源码中
- 1,Checking the input and output specifications of the job.
- 2,Computing the InputSplits for the job. // split ->并行度和计算向数据移动就可以实现了
- 3,Setup the requisite accounting information for the DistributedCache of the job, if necessary.
- 4,Copying the job’s jar and configuration to the map-reduce system directory on the distributed file-system.
- 5,Submitting the job to the JobTracker and optionally monitoring it’s status
对于 split 的计算:
minSize = 1
maxSize = Long.Max
blockSize = file
splitSize = Math.max(minSize, Math.min(maxSize, blockSize));
//默认split大小等于block大小
切片split是一个窗口机制:(调大split改小,调小split改大)切片信息:
split:解耦 存储层和计算层
1,file
2,offset
3,length
4,hosts //支撑的计算向数据移动
MapTask 阶段
input -> map方法 -> output
- 格式化记录(TextInputFormat->
LineRecordreader
),以记录为单位调用 map 方法;- map 输出
<K,V >
的记录,通过分区器算出分区 P 形成三元组,序列化成字节数组后写到环形缓冲区- 环形缓冲区两端方 <K,V> 索引;索引是固定长度16B:1) P 2) Key Start 3) Value Start 4)Value Length
- 默认达到环形缓冲区的 80 % 溢写磁盘;快速排序80%数据,同时map输出的线程向剩余的空间写;快速排序的过程:是比较key排序,但是移动的是索引
- 溢写磁盘的过程发生二次排序,即分区有序,分区内key有序;
- 对多次溢写的小文件进行合并,combiner
ReduceTask 阶段
- Reduce Task 从 MRAppMaster 得知,对应分区内数据所在的位置,拉取对应分区的数据;
- ReduceTask 拉取数据到磁盘,之后合并文件进行归并排序;
- 按照一组记录为单位调用 reduce 方法;
- 结果写入磁盘;
3、MapReduce 过程优化
小文件优化:
1、从存储方面来说:hadoop的存储每个文件都会在NameNode上记录元数据,如果同样大小的文件,文件很小的话,就会产生很多文件,造成NameNode的压力。
2、从读取方面来说:同样大小的文件分为很多小文件的话,会增加磁盘寻址次数,降低性能
3、从计算方面来说:我们知道一个map默认处理一个分片或者一个小文件,如果map的启动时间都比数据处理的时间还要长,那么就会造成性能低,而且在map端溢写磁盘的时候每一个map最终会产生reduce数量个数的中间结果,如果map数量特别多,就会造成临时文件很多,而且在reduce拉取数据的时候增加磁盘的IO。
怎么处理
- 从源头干掉,也就是在hdfs上我们不存储小文件,也就是数据上传hdfs的时候我们就合并小文件
- 在FileInputFormat读取入数据的时候我们使用实现类CombineFileInputFormat读取数据,在读取数据的时候进行合并。
- 使用hadoop 自带的 archive
MapReduce 过程优化
提交阶段:
对于小文件:可以使用合适的文件的读取类,进行小文件合并
任务信息:上传的 Jar、分片信息,等资源信息会提交到 hdfs 的临时目录中,默认会有10个副本;可以通过参数mapreduce.client.submit.file.replication
控制副本的数量(集群规模较大的时候);
MapTask 阶段:
Split 大小:一个 split 会启动一个 MapTask 执行,所以需要合理设置 split 大小;
Math.max(minSize, Math.min(maxSize, blockSize))
MapTask 资源:mapreduce.map.memory.mb
默认1G,mapreduce.map.cpu.vcores
默认为 1
Map 任务向磁盘溢写之前会先写入环形缓冲区,可以较少溢写的次数,减少磁盘IO;当达到磁盘文件达到一定量时,会进行小文件的合并,可以增大合并文件的数量;设置环形缓冲区大小:
mapreduce.task.io.sort.mb
设置环形缓冲区溢写阈值:
mapreduce.map.sort.spill.percent
设置合并磁盘文件个数:
mapreduce.task.io.sort.factor
Map 输出文件的数据压缩可以减少数据量的大小:
mapreduce.map.output.compress(default:false)
压缩的方式:
mapreduce.map.output.compress.codec
mapreduce.task.io.sort.mb #排序map输出所需要使用内存缓冲的大小,以兆为单位, 默认为100
mapreduce.map.sort.spill.percent #map输出缓冲和用来磁盘溢写过程的记录边界索引,这两者使用的阈值,默认0.8
mapreduce.task.io.sort.factor #排序文件时,一次最多合并的文件数,默认10
mapreduce.map.output.compress #在map溢写磁盘的过程是否使用压缩,默认false
org.apache.hadoop.io.compress.SnappyCodec #map溢写磁盘的压缩算法,默认org.apache.hadoop.io.compress.DefaultCodec
mapreduce.shuffle.max.threads #该参数表示每个节点管理器的工作线程,用于map输出到reduce,默认为0,表示可用处理器的两倍
ReduceTask 阶段:
- Reduce 个数设置:
mapreduce.job.reduces
- Reduce 资源设置:
mapreduce.reduce.memory.mb
,mapreduce.reduce.cpu.vcores
- Reduce 进行数据拉取时,可以设置拉取时的并行度 :
mapreduce.reduce.shuffle.parallelcopies
- Reduce Task 下载任务时,最大的下载时间段可以设置 :
mapreduce.reduce.shuffle.read.timeout
- Reduce Task 拉取过来的数据会先放入内存缓冲区,当达到一定量之后才溢写到磁盘; 该内存基于 JVM 的堆大小;
mapreduce.reduce.s huffle.input.buffer.percent(default 0.7)
shuffle 在 reduce 内存中的最大值为: 0.7 × maxHeap of reduce task,内存到磁盘的阈值:mapreduce.reduce.shuffle.merge.percent(default0.66)
- 对于拉取的数据,进行归并排序时的合并因子:
mapreduce.task.io.sort.factor
对于 map 输出很多,可以选择提高该值;
mapreduce.reduce.shuffle.parallelcopies #把map输出复制到reduce的线程数,默认5
mapreduce.task.io.sort.factor #排序文件时一次最多合并文件的个数
mapreduce.reduce.shuffle.input.buffer.percent #在shuffle的复制阶段,分配给map输出缓冲区占堆内存的百分比,默认0.7
mapreduce.reduce.shuffle.merge.percent #map输出缓冲区的阈值,用于启动合并输出和磁盘溢写的过程