一、Spark Shuffle介绍?
1、Shuffle—>“洗牌”,
在Spark中Shuffle的目的是为了保证每一个key所对应的value都会汇聚到同一个节点上去处理和聚合。
2、Spark中,什么情况下会发生shuffle?
reduceByKey、groupByKey、sortByKey、countByKey、join等操作。
3、Spark中的Shuffle包括两种:
HashShuffle
SortShuffle
二、HashShuffle运行原理
SortShuffle的运行机制分成两种:
普通运行机制:
Shuffle Write阶段:
由于Stage后面紧跟了另一个Stage,所以数据落地会发生在Shuffle Write阶段。为了将分区中数据相同的key写入一个分区文件中,需要将task计算结果的key的hashcode值与Reduce task个数取模,从而确定将结果写入哪个分区文件中,这样即可保证相同的key在一个分区文件中。为了加快向磁盘写文件的速度,需要事先设置一个buffer作为缓存,每个buffer的大小是32K。
Shuffle Red阶段:
Reduce task从上个Stage的task节点中拉取属于自己的分区文件,这样即可保证每一个Key所对应的Value都会在同一个节点上。拉取的过程属于现拉现用。
合并机制:
倘若Executor只有一个core,所以每次只能有一个task执行,当第一个task执行完成后,第二个task会复用第一个task所创建的buffer和磁盘文件,从而减少磁盘文件的个数。
三、SortShuffle运行原理
SortShuffle的运行机制也分成两种:
普通运行机制:
由于Spark只是一个计算框架,没有办法严格控制Executor内存,只能采取监控的方式去监控内存的情况,内存数据初始值的大小大约默认为5M,当超出5M的时候,例如5.02M,监控内存数据的对象就会再去申请5.02*2-5=5.04M内存,如果申请到内存就不必进行溢写,否则再进行溢写。
Shuffle Write阶段:
溢写到磁盘的过程与MapReduce的ShuffleWrite阶段一样。
区别:
内存数据初始值是5M大小,它可以申请扩大,而MapReduce中的Buffer是固定的100M。
溢写除了生成磁盘文件,还有索引文件,索引文件是对磁盘文件的描述,还有记录每个分区的起始位置以及终止位置。
Shuffle Red阶段:
Reduce Task首先会解读索引文件,然后拉取相应分区的数据,然后再处理。
bypass运行机制
bypass运行机制与普通运行机制相比,缺少了忘内存数据存放、排序。除此之外,其余流程一样。
bypass运行机制的触发条件:
shuffle reduce task数量小于spark.shuffle.sort.bypassMergeThreshold参数的值。
四、Shuffle过程中,磁盘小文件寻址问题
1、Map task执行完毕后,将自己的执行结果信息(比如:磁盘小文件的位置、最终执行状态信息)封装到mapstatus中,然后调用本进程中的MapOutputTrackerWorker,将mapstatus对象发送给Driver中的MapOutputTrackerWorker。
2、Reduce task会调用自己进程中的MapOutputTrackerWorker对象去向MapOutputTrackerWorker获取磁盘小文件的位置信息,然后将位置信息给BlockManagerSlave去拉取磁盘小文件。(默认会启动5个子线程去拉取数据,但5个子线程总共拉取的数据量不能超过64M)。拉来的数据会存在shuffle聚合内存中(20%x80%),以供Reduce task计算。
注:
Reduce task中的计算模式也是pipeline管道计算模式。
五、Shuffle可能面临的问题
由HashShuffle的流程图可知:磁盘小文件(分区文件)的个数=m(map task num)*r(reduce task num)
当磁盘小文件过多时,带来的问题有:
write阶段会创建大量写文件的对象
read阶段拉取数据需要进行多次网络传输
read阶段会创建大量读文件的对象
读写对象过多造成JVM内存不足,从而导致内存溢出
六、Executor的内存管理
Excutor内存管理方式有两种:
静态的内存管理:
unroll:用来进行反序列化。
两块防止OOM的内存,可以供JVM使用
统一的内存管理:
统一内存管理中,75%中的两块内存可以互相借用,但是有最大限度。
七、Shuffle调优
1、spark.shuffle.file.buffer
默认值:32k
参数说明:该参数用于设置shuffle write task的BufferedOutputStream的buffer缓冲大小。将数据写到磁盘文件之前,会先写入buffer缓冲中,待缓冲写满之后,才会溢写到磁盘。
调优建议:如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如64k),从而减少shuffle write过程中溢写磁盘文件的次数,也就可以减少磁盘IO次数,进而提升性能。在实践中发现,合理调节该参数,性能会有1%~5%的提升。
2、spark.reducer.maxSizeInFlight
默认值:48M
参数说明:该参数用于设置shuffle read task的buffer缓冲大小,而这个buffer缓冲决定了每次能够拉取多少数据。
调优建议:如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如96m),从而减少拉取数据的次数,也就可以减少网络传输的次数,进而提升性能。在实践中发现,合理调节该参数,性能会有1%~5%的提升。
3、spark.shuffle.io.maxRetries
默认值:3
参数说明:shuffle read task从shuffle write task所在节点拉取属于自己的数据时,如果因为网络异常导致拉取失败,是会自动进行重试的。该参数就代表了可以重试的最大次数。如果在指定次数之内拉取还是没有成功,就可能会导致作业执行失败。
调优建议:对于那些包含了特别耗时的shuffle操作的作业,建议增加重试最大次数(比如60次),以避免由于JVM的full gc或者网络不稳定等因素导致的数据拉取失败。在实践中发现,对于针对超大数据量(数十亿~上百亿)的shuffle过程,调节该参数可以大幅度提升稳定性。
shuffle file not find taskScheduler不负责重试task,由DAGScheduler负责重试stage
4、spark.shuffle.io.retryWait
默认值:5s
参数说明:具体解释同上,该参数代表了每次重试拉取数据的等待间隔,默认是5s。
调优建议:建议加大间隔时长(比如60s),以增加shuffle操作的稳定性。
5、spark.shuffle.memoryFraction
默认值:0.2
参数说明:该参数代表了Executor内存中,分配给shuffle read task进行聚合操作的内存比例,默认是20%。
调优建议:在资源参数调优中讲解过这个参数。如果内存充足,而且很少使用持久化操作,建议调高这个比例,给shuffle read的聚合操作更多内存,以避免由于内存不足导致聚合过程中频繁读写磁盘。在实践中发现,合理调节该参数可以将性能提升10%左右。
6、spark.shuffle.manager
默认值:sort
参数说明:该参数用于设置ShuffleManager的类型。Spark 1.5以后,有三个可选项:hash、sort和tungsten-sort。HashShuffleManager是Spark 1.2以前的默认选项,但是Spark 1.2以及之后的版本默认都是SortShuffleManager了。tungsten-sort与sort类似,但是使用了tungsten计划中的堆外内存管理机制,内存使用效率更高。
调优建议:由于SortShuffleManager默认会对数据进行排序,因此如果你的业务逻辑中需要该排序机制的话,则使用默认的SortShuffleManager就可以;而如果你的业务逻辑不需要对数据进行排序,那么建议参考后面的几个参数调优,通过bypass机制或优化的HashShuffleManager来避免排序操作,同时提供较好的磁盘读写性能。这里要注意的是,tungsten-sort要慎用,因为之前发现了一些相应的bug。
7、spark.shuffle.sort.bypassMergeThreshold
默认值:200
参数说明:当ShuffleManager为SortShuffleManager时,如果shuffle read task的数量小于这个阈值(默认是200),则shuffle write过程中不会进行排序操作,而是直接按照未经优化的HashShuffleManager的方式去写数据,但是最后会将每个task产生的所有临时磁盘文件都合并成一个文件,并会创建单独的索引文件。
调优建议:当你使用SortShuffleManager时,如果的确不需要排序操作,那么建议将这个参数调大一些,大于shuffle read task的数量。那么此时就会自动启用bypass机制,map-side就不会进行排序了,减少了排序的性能开销。但是这种方式下,依然会产生大量的磁盘文件,因此shuffle write性能有待提高。
问题思考
Application在执行的过程中,出现了类似reduce OOM的错误原因以及解决办法?
三种解决方案:
1、提高Executor的内存
2、提高shuffle聚合的内存比例
3、减少每次拉取的数据量