什么是Spark Shuffle?
- reduceByKey的含义?
- reduceByKey会将上一个RDD中的每一个key对应的所有value聚合成一个value,然后生成一个新的RDD,元素类型是<key,value>对的形式,这样每一个key对应一个聚合起来的value
- 问题
- 每一个key对应的value不一定都是在一个partition中,也不太可能在同一个节点上,因为RDD是分布式的弹性的数据集,他的partition极有可能分布在各个节点上
- 如何让聚合?
- Shuffle Write:上一个stage的每个map task就必须保证将自己处理的当前分区中的数据相同的key写入一个分区中,可能会写入多个不同的分区文件中
- Shuffle Read:reduce task就会从上一个stage的所有task所在的机器上寻找属于自己的那些分区文件,这样就可以保证每一个key所对应的value都会汇聚到同一个节点上去处理和聚合
Spark Shuffle的迭代历史
-
ShuffleManager随着Spark的发展有两种实现的方式:分别是:HashShuffleManager(spark1.2之前使用)和SortShuffleManager,因此spark的Shuffle有Hash Shuffle和Sort Shuffle两种
-
两者的比较:在Spark1.2之前,默认的Shuffle计算引擎就是HashShuffleManager,但是HashShuffleManager有着一个非常严重的弊端,就是会产生大量的中间磁盘文件,导致会产生大量的IO操作,从而影响计算的性能。因此,在Spark1.2以后,默认的Shuffle计算引擎就改成了SortShuffleManager,该方式相较于HashShuffleManager来说,有了一定的改进。主要就在于,每个Task在进行Shuffle操作时,虽然也会产生大量的临时磁盘文件,但是最后会将所有的临时磁盘文件合并成一个,因此每个Task就对应只有一个磁盘文件。在下一个stage的Shuffle read task拉取自己的数据时,只要根据索引来读取每个磁盘文件中所对应的部分数据即可
Spark Shuffle的四种策略
1.最初始的HashShuffleManager
-
工作机制:对key执行hash算法,从而将相同的key都写入到一个磁盘文件中,而每一个磁盘文件都只属于下游stage的一个task。在将数据写入磁盘之前,会先将数据写入到内存缓冲,当内存缓冲填满之后,才会溢写到磁盘文件中
-
不足:下游有几个task,上游的每一个task就要创建几个临时文件,每个文件中只存储key取hash之后相同的数据,导致当下游task的任务过多时,上游会堆积大量的小文件,从而影响性能(总结一下就是:一对多的关系)
2.经过优化的HashShuffleManager
引入了consolidate机制(文件合并机制)
-
工作机制:在Shuffle Write过程中,上游task就不会为下游stage的每个task创建一个磁盘文件了。此时会出现ShuffleFileGroup的概念,每个shuffleFileGroup会对应一批磁盘文件,磁盘文件的数量与下游stage的task数量相同。一个Executor上有多少个CPU core,就可以并行执行多少个task。而第一批并行执行的每个task都会创建一个shuffleFileGroup,并将数据写入对应的磁盘文件中。当Executor的CPU core执行完一批task,接着执行下一批task时,下一批task就会复用之前已有的shuffleFileGroup,包括其中的磁盘文件。也就是说:此时task会将数据写入已有的磁盘文件中,而不是写入新的磁盘文件中。因此,consolidate机制允许不同的task复用同一批磁盘文件,这样就可以有效地将多个task的磁盘文件进行一定程度上的合并,从而大幅度减少磁盘文件的数量,进而提升Shuffle Write的性能
-
触发consolidateFiles的条件:
- 需要将spark.shuffle.consolidateFiles调整为true(默认实际开启的)
总结:
未经优化:
上游的task数量:m
下游的task数量:n
上游的executor数量:k (m>=k)
总共的磁盘文件:m*n
优化之后的:
上游的task数量:m
下游的task数量:n
上游的executor数量:k (m>=k)
总共的磁盘文件:k*n
3.SortShuffleManager普通机制
-
工作机制:在普通模式下,数据先写入一个内存数据结构中,此时根据不同的shuffle算子,可以选用不容的数据结构:如果是由聚合操作的shuffle算子,就是用map的数据结构(边聚合边写入内存),如果是join的算子,就使用array的数据结构(直接写入内存)。接着,每写一条数据进入内存数据结构之后,就会判断是否达到了某个临界值,如果达到了临界值的话,就会尝试的将内存数据结构中的数据溢写到磁盘,然后清空内存数据结构
在溢写到磁盘文件之前,会先根据key对内存数据结构中已有的数据进行排序,排序之后,会分批将数据写入磁盘文件,默认的batch数量是10000条,也就是说,排序好的数据,会以每批次1万条数据的形式分批写入磁盘文件,写入磁盘文件是通过Java的BufferedOutputStream实现的。BufferedOutputStream是Java的缓冲输出流,首先会将数据缓冲在内存中,当内存缓冲满溢之后再一次性写入磁盘文件中。这样可以减少磁盘IO次数,提升性能
此时task将所有数据写入内存数据结构的过程,会发送多次磁盘溢写,产生多个临时文件,最后会将之前所有的临时文件都进行合并,最后会合并成为一个大文件。最终只剩下两个文件,一个是合并之后的数据文件,一个是索引文件(标识了下游各个task的数据再文件中的start offset与end offset)。最终由下游的task根据索引文件读取相应的数据文件
4.SortShuffleManager-bypass机制(默认)
-
工作机制:此时task会为每个下游task都创建一个临时磁盘文件,并将数据按key进行hash,然后根据key的hash值,将key写入对应的磁盘文件中。当然,写入磁盘文件时,也是先写入内存换成,缓冲写满之后再溢写到磁盘文件。最后将所有临时磁盘文件合并成一个磁盘文件,并创建一个单独的索引文件
-
bypass机制与普通机制的不同:
- 磁盘写机制不同
- 不会进行排序
也就是说,启用该机制最大好处在于:ShuffleWrite过程中,不需要进行数据的排序操作,也就节省掉了这部分的性能开销
-
触发bypass机制的条件:
- shuffle map task的数量小于spark.shuffle.sort.bypassMergeThreshold参数的值(默认200)或者不是聚合类的shuffle算子(比如groupByKey)
SparkShuffle调优
主要是调整缓冲的大小,拉取次数,重试次数与等待时间,内存比例分配,是否继续排序操作等等
1.spark.shuffle.file.buffer
- 参数说明:该参数用于设置shuffle write task的BufferedOutputStream的buffer缓冲大小(默认是32K)。将数据写入磁盘文件之前,会先写入buffer缓冲中,待缓冲写满之后,才会溢写到磁盘
- 调优建议:如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如64K),从而减少shuffle write过程中溢写磁盘文件的次数,也就可以减少磁盘IO次数,进而提高性能。在时间中发现,合理调节该参数,性能会由1%~5%的提升
2.spark.reducer.maxSizeInFlight
- 参数说明:该参数用于设置shuffle read task的buffer缓冲大小,而这个buffer缓冲决定了每次能够拉取多少数据。
- 调优建议:如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如96m),从而减少拉取数据的次数,也就可以减少网络传输的次数,进而提升性能。在实践中发现,合理调节该参数,性能会有1%~5%的提升
3.spark.shuffle.io.maxRetries and spark.shuffle.io.retryWait
- spark.shuffle.io.maxRetries
- 参数说明:shuffle read task从shuffle write task所在节点拉取属于自己的数据时,如果因为网络异常导致拉取失败,是会自动进行重试的。该参数就代表了可以重试的最大次数。(默认是3次)
- spark.shuffle.io.retryWait
- 参数说明:该参数表示每次重试拉取数据的等待间隔(默认为5s)
- 调优建议:一般的调优都是将重试次数调高,不调整时间间隔
4.spark.shuffle.memoryFraction
- 参数说明:该参数表示Executor内存中,分配给shuffle read task进行聚合操作的内存比例
5.spark.shuffle.manager
- 参数说明:设置ShuffleManager的类型(默认sort)
- Spark1.5之后有三个可选项
- Hash
- Sort
- tungsten-sort
6.spark.shuffle.sort.bypassMergeThreshold
- 参数说明:当ShuffleManager为SortShuffleManager时,如果shuffle read task的数量小于这个阈值(默认200),则shuffle write过程中不会进行排序操作
- 调优建议:当你使用SortShuffleManager时,如果的确不需要排序操作,那么建议将这个参数调大些
7.spark.shuffle.consolidateFiles
- 参数说明:如果使用HashShuffleManager,该参数有效,如果设置为true,那么就会开启consolidate机制,也就是开启优化后的HashShuffleManager,会对shuffleFileGroup进行复用
- 调优建议:如果的确不需要SortShuffleManager的排序机制,那么除了使用bypass机制,还可以尝试将spark.shffle.manager参数手动指定为hash,使用HashShuffleManager,同时开启consolidate机制。在实践中尝试过,发现其性能比卡起了bypass机制的SortShuffleManager要高出10%~30%
ark.shffle.manager参数手动指定为hash,使用HashShuffleManager,同时开启consolidate机制。在实践中尝试过,发现其性能比卡起了bypass机制的SortShuffleManager要高出10%~30%