一、mapreduce
1.工作机制
第一步,准备好文件;
第二步,切片分析;
第三步,客户端会提交3个信息:Job的切片、jar包(集群模式才有)、Job运行相关的参数信息;
第四步,Yarn会开启一个Mr appmaster(整个任务的老大),Mr appmaster会读取客户端提交的信息,根据切片信息开启对应个数的MapTask;
第五步,MapTask开启InputFormat(默认TextInputFormat)来按行读取对应切片内容,生成对应的kv(K是偏移量,V是每一行的内容),然后返回给Mapper;
第六步,读取数据后,调用jar中编写的mapper运行map函数,调用编写的运算逻辑;
第七步,数据被写入到环形缓冲区中;
(缓冲区中数据对<k,v>从右半边开始写入,索引从左半边开始写入;存储规则按照keystart---valstart---keystart---valstart...的形式;(keystart和valuestart之间指向的内容就是key数据,valuestart和keystart之间指向的内容就是value数据))
环形缓冲区不需要重新申请新的内存,始终用的都是这个内存空间。MR是用java写的,而Java有一个最讨厌的机制就是Full GC。Full GC总是会出来捣乱,这个bug也非常隐蔽,发现了也不好处理。环形缓冲区从头到尾都在用那一个内存,不断重复利用,因此完美的规避了Full GC导致的各种问题,同时也规避了频繁申请内存引发的其他问题。
第八步中,排序方法是快排,排序指的是在溢写前排序,而不是写进去的时候就排序;排序针对的对象不是数据本身,而是数据对应的索引,而后按照字典的顺序排;(不同分区中的数据会被分配到不同的reduce任务中);分区数决定ReduceTask;
第九步,把排好序的内容溢写到磁盘文件中;
第十步,对溢写文件做归并排序(即图中左半部分和右半部分文件分别做归并排序);
第十一步,这一步不是一定发生的,这是一个预聚合的过程,即提前进行聚合然后再归并排序,相当于是特殊预聚合情况下的第十步;
第十二步,所有MapTask任务完成后,启动相应数量的ReduceTask(数目等于设定的分区数(不是绝对的,有可能先有一部分MapTask先进行了Reduce过程,可进行配置)(这一步可以不说明,直接说明Reducer任务会主动从Mapper任务复制其输出的键值对即可)
第十三步,ReduceTask拿到数据后进行合并,然后再进行一次归并排序(不同MapTask的同一分区数据放到一起做归并排序),这样后续就能很容易把相同key的内容发送到1个Reduce方法中;其中groupingComparator(可选)主要判断key值是否相同,决定数据是否会被划分为一组。如果有按照其规则判断;若不存在则必须内容完全相同则相同
第十四步,一次读取一组内容到Reduce方法中(也即key相同的一组数据);
第十五步,由OutputFormat向外写出数据;
2.shuffle工作机制
map阶段之后,reduce之前的阶段称为shuffle。即mr工作原理的第七步到第十三步均是。
3.Hadoop优化
1)数据输入(小文件过多):
合并小文件:在执行map前合并小文件
采用CombineTextInputFormat作为输入
2)Map 阶段
- 增大环形缓冲区大小。由 100m 扩大到 200m
- 增大环形缓冲区溢写的比例。由 80%扩大到 90%
- 减少对溢写文件的 merge 次数。(10 个文件,一次 20 个 merge)
- 不影响实际业务的前提下,采用 Combiner 提前合并,减少I/O。
3)Reduce 阶段
- 合理设置 Map 和 Reduce 数:两个都不能设置太少,也不能设置太多。太少,会导致 Task 等待,延长处理时间;太多,会导致 Map、Reduce 任务间竞争资源,造成处理超时等错误。
- 设置 Map、Reduce 共存:调整 slowstart.completedmaps 参数,使 Map 运行到一定程度后,Reduce 也开始运行,减少 Reduce 的等待时间。
- 规避使用 Reduce,因为 Reduce 在用于连接数据集的时候将会产生大量的网络消耗。
- 增加每个 Reduce 去 Map 中拿数据的并行数
- 集群性能可以的前提下,增大 Reduce 端存储数据内存的大小。
4)IO 传输
采用数据压缩的方式,减少网络 IO 的的时间。安装 Snappy 和 LZOP 压缩编码器。
压缩:
- map 输入端主要考虑数据量大小和切片,支持切片的有 Bzip2、LZO。注意:LZO 要想支持切片必须创建索引;
- map 输出端主要考虑速度,速度快的 snappy、LZO;
- reduce 输出端主要看具体需求,例如作为下一个mr输入需要考虑切片,永久保存考虑压缩率比较大的 gzip。
5)其他
- (1)NodeManager 默认内存 8G,需要根据服务器实际配置灵活调整,例如 128G 内存,配置为100G 内存左右,yarn.nodemanager.resource.memory-mb。
- (2)单任务默认内存 8G,需要根据该任务的数据量灵活调整,例如 128m 数据,配置 1G 内存,yarn.scheduler.maximum-allocation-mb。
- (3)mapreduce.map.memory.mb :控制分配给MapTask 内存上限,如果超过会kill掉进程(报错:Container is running beyond physical memory limits. Current usage:565MB of512MBphysical memory used;Killing Container)。默认内存大小为 1G,如果数据量是 128m,正常不需要调整内存;如果数据量大于 128m,可以增加 MapTask 内存,最大可以增加到 4-5g。
- (4)mapreduce.reduce.memory.mb:控制分配给ReduceTask内存上限。默认内存大小为1G,如果数据量是128m,正常不需要调整内存;如果数据量大于 128m,可以增加 ReduceTask 内存大小为 4-5g。
- (5)mapreduce.map.java.opts:控制 MapTask 堆内存大小。(如果内存不够,报java.lang.OutOfMemoryError)
- (6)mapreduce.reduce.java.opts:控制 ReduceTask 堆内存大小。(如果内存不够,报java.lang.OutOfMemoryError)
- (7)可以增加 MapTask 的 CPU 核数,增加 ReduceTask 的 CPU 核数
- (8)增加每个 Container 的 CPU 核数和内存大小
- (9)在 hdfs-site.xml 文件中配置多目录(多磁盘)
- (10)NameNode 有一个工作线程池,用来处理不同 DataNode 的并发心跳以及客户端并发的元数据操作。比如集群规模为 8 台时,此参数设置为 41。
二、spark
shuffle
HashShuffle
- Map Task将数据写入buffer缓冲区,待缓冲区达到阈值时开始溢写文件,文件数量取决于(等于)Reduce Task的数量。Reduce Task的数量取决于(等于)上一个Stage的最后一个RDD的分区数。
- Reduce Task一边拉取对应分区的数据到buffer缓存中,一边进行处理,使用map方法对数据进行归并,直至所有的数据归并结束。
- 每个map task都会为下游的每个reduce task创建一个磁盘文件(容易产生大量小文件,影响性能)
优化的HashShuffle
方法:设置consolidateFiles为true
- 一个executor为一个reduce task生成一个磁盘文件。
- consolidateFiles机制允许多个map task复用同一个磁盘文件,可以在一定程度上对map task的数据进行合并,从而大幅减小磁盘文件的数量,提升shuffle write性能。
SortShuffle(spark2.0后默认shuffle)
- map task会先将数据写入到一个数据结构中(reduceByKey写入Map,一边通过Map局部聚合,一边写入内存。Join算子写入ArrayList直接写入内存中。),当内存缓冲区中的数据达到一定阈值时,开始进行溢写。
- 溢写到磁盘之前,先按照数据的key进行排序,排序后分批次写入磁盘, 默认每批次1w条数据。
- 数据溢写到磁盘的过程中会产生多个临时磁盘文件,临时磁盘文件会进行merge归并,最终一个map task只会生成一个归并后的磁盘文件,同时还是生成一个对应的索引文件,记录数据的offset信息。
- reduce task拉取对应分区的数据进行处理。
bypass SortShuffle
满足条件:
- 不是聚合类型的shuffle算子
- map task的数量小于bypassMergeThreshold的值,默认是200。
优化:
- map task数据溢写到磁盘之前不会对数据进行排序。
- 数据的溢写规则和未经优化的hashshuffle一样,每个map task都会为下游的每个reduce task生成一个文件,但是最后会进行数据的归并,合并为一个磁盘文件。