Spark Shuffle操作

什么是Spark Shuffle?

在Spark中,数据通常不会跨分区分布,以满足特定操作的需要。在计算期间,单个任务将对单个分区进行操作——因此,要组织单个reduceByKey 的计算任务要执行的所有数据,Spark需要执行一个all-to-all操作。它必须从所有分区中读取所有键的所有值,然后将所有分区的值放在一起计算每个键的最终结果——这称为shuffle。

Spark中的某些操作会触发称为shuffle的事件。shuffle是Spark用于重新分发数据的机制,以便跨分区对数据进行不同的分组。这通常涉及跨执行程序和机器复制数据,使shuffle成为一项复杂而昂贵的操作。在Spark Core中,Shuffle是划分宽窄依赖依据Stage的依据。

Background(背景)

为了理解shuffle过程中会发生什么,我们可以考虑reduceByKey操作的例子。reduceByKey操作生成一个新的RDD,其中单个键的所有值组合成一个元组——键和对与该键关联的所有值执行reduce函数的结果。挑战在于,并不是一个键的所有值都必须位于相同的分区,甚至是相同的机器上,但是它们必须位于同一个位置来计算结果。

尽管重新shuffle数据的每个分区中的元素集是确定的,分区本身的顺序也是确定的,但是这些元素的顺序不是。如果你想在shuffle之后获得可预测的有序数据,那么可以使用:

(1).使用mapPartitions对每个分区进行排序的配置,例如 .sorted.
(2).repartitionandsortwithinpartition可以在重新分区的同时有效地对分区进行排序.
(3).sortBy生成一个全局有序的RDD.

会引起shuffle 的操作包括重分区操作(如repartition 和 coalesce)、ByKey操作(除计数外)(如groupByKey和reduceByKey)以及join操作(如cogroup和join)。

Performance Impact(性能影响)

Shuffle是一种昂贵的操作,因为它涉及磁盘I/O、数据序列化和网络I/O。要为shuffle组织数据,Spark生成一组任务—映射任务来组织数据,并生成一组reduce任务来聚合数据。这个命名法来自MapReduce,并不直接与Spark的map和reduce操作相关。

在内部,来自单个映射任务的结果被保存在内存中,直到它们无法匹配为止。然后,根据目标分区对它们进行排序,并将其写入单个文件。在reduce端,任务读取相关的已排序的blocks块。

某些shuffle 操作会消耗大量堆内存,因为它们在传输记录之前或之后使用内存中的数据结构来组织记录。具体来说,reduceByKey和aggregateByKey在映射端创建这些结构,ByKey操作在reduce端生成这些结构。当数据不适合内存时,Spark会将这些表溢出到磁盘,导致磁盘I/O的额外开销和垃圾收集的增加。

Shuffle还会在磁盘上生成大量的中间文件。从Spark 1.3开始,这些文件一直保存到不再使用相应的rdd并进行垃圾收集为止。这样做是为了在重新计算沿袭时不需要重新创建shuffle文件。如果应用程序保留对这些rdd的引用,或者GC不经常启动,那么只有在很长一段时间后才会发生垃圾收集。这意味着长时间运行的Spark作业可能会消耗大量磁盘空间。临时存储目录由spark.local指定。配置Spark上下文时的dir配置参数。

Configuration(配置参数)

Shuffle行为可以通过调整各种配置参数来进行调整。如下所示:

属性名称 默认值 含义
spark.reducer.maxSizeInFlight 48m 同时从每个reduce任务获取的映射输出的最大大小,以MiB表示,除非另外指定。由于每个输出都需要我们创建一个缓冲区来接收它,所以这表示每个reduce任务的内存开销是固定的,因此保持较小的内存,除非您有大量的内存。
spark.reducer.maxReqsInFlight Int.MaxValue 这种配置限制了在任何给定点获取块的远程请求的数量。当集群中的主机数量增加时,可能会导致到一个或多个节点的大量入站连接,导致worker在负载下失败。通过允许它限制获取请求的数量,可以减轻这种情况。
spark.reducer.maxBlocksInFlightPerAddress Int.MaxValue 这种配置限制了每个reduce任务从给定主机端口获取的远程块的数量。当在一次获取或同时从给定地址请求大量块时,这可能会导致服务执行程序或节点管理器崩溃。这对于在启用外部shuffle时减少节点管理器上的负载特别有用。您可以通过将其设置为较低的值来减轻这个问题。
spark.maxRemoteBlockSizeFetchToMem Int.MaxValue - 512 当块的大小(以字节为单位)超过此阈值时,远程块将被提取到磁盘。这是为了避免占用太多内存的巨大请求。默认情况下,这只对块> 2GB启用,因为无论可用的资源是什么,这些块都不能直接提取到内存中。但是它可以降低到一个更低的值。200米),以避免在较小的块上使用太多内存。请注意,此配置将同时影响shuffle fetch和block manager远程块获取。对于启用外部shuffle服务的用户,此功能只能在外部shuffle服务比Spark 2.2更新时使用。是否压缩映射输出文件。一般来说是个好主意。压缩将使用spark.io.compression.codec。
spark.shuffle.file.buffer 32k 每个shuffle文件输出流的内存缓冲区的大小,除非另外指定,以KiB为单位。这些缓冲区减少了在创建中间shuffle文件时进行的磁盘查找和系统调用的数量。调优建议:内存足,调大(比如64k)
spark.shuffle.io.maxRetries 3 (Netty only)如果设置为非零值,则会自动重试由于io相关异常而失败的获取。这种重试逻辑有助于在面对长时间GC暂停或短暂的网络连接问题时稳定大型shuffle。对于超大数据量(数十亿~上百亿)的shuffle过程,调大该参数可大幅度提升稳定性。
spark.shuffle.io.numConnectionsPerPeer 1 主机之间的连接被重用,以减少大型集群的连接累积。对于具有许多硬盘和少量主机的集群,这可能导致并发性不足,无法使所有磁盘饱和,因此用户可能会考虑增加这个值。
spark.shuffle.io.preferDirectBufs true (仅适用于Netty)堆外缓冲区用于减少洗牌和缓存块传输期间的垃圾收集。对于堆外内存受到严格限制的环境,用户可能希望关闭此功能,以强制Netty的所有分配都处于堆上。
spark.shuffle.io.retryWait 5s (仅适用于Netty)两次获取之间等待的时间。默认情况下,重试导致的最大延迟为15秒,计算方法为maxRetries * retryWait。调优建议:加大间隔时长(比如60s),以增加shuffle操作的稳定性。
spark.shuffle.service.enabled false 启用外部shuffle服务。此服务保留由执行程序编写的shuffle文件,以便可以安全地删除执行程序。如果spark.dynamicAllocation必须启用此功能。使是“真正的”。必须设置外部shuffle服务才能启用它。有关更多信息,请参见动态分配配置和设置文档。
spark.shuffle.service.port 7337 外部shuffle服务将在其上运行的端口。
spark.shuffle.service.index.cache.size 100m 以字节为单位的指定内存占用限制的缓存项。
spark.shuffle.maxChunksBeingTransferred Long.MAX_VALUE 在shuffle服务上允许同时传输的最大块数。注意,当命中最大数字时,将关闭新的传入连接。客户端将根据shuffle重试配置重试(参见spark. shuffley .io)。,如果达到这些限制,则任务将在获取失败时失败。
spark.shuffle.sort.bypassMergeThreshold 200 当ShuffleManager为SortShuffleManager时,如果shuffle read task的数量小于这个阈值,则shuffle write过程中不会进行排序操作,按照未经优化的HashShufflemanager的方式去写数据。调优建议:使用SortShuffleManager时,且不需要排序操作,将这个参数调大,大于shuffle read task的数量。
spark.shuffle.accurateBlockThreshold 100 * 1024 * 1024 以字节为单位的阈值,在该阈值之上,可以准确记录HighlyCompressedMapStatus中的shuffle块的大小。这有助于通过避免在获取shuffle块时低估shuffle块大小来防止OOM
spark.shuffle.registration.timeout 5000 注册到外部shuffle服务的超时(以毫秒为单位)。
spark.shuffle.registration.maxAttempts 3 当我们注册到外部shuffle服务失败时,我们将重试最大尝试次数。
spark.shuffle.memoryFraction 0.2 用于reduce端聚合的内存比例,超过20%就会溢出到磁盘上。调优建议:内存足且很少使用持久化操作,调高这个比例,避免由于内存不足导致聚合过程中频繁读写磁盘

无论是MapReduce还是Spark,Shuffle阶段是最重要的阶段,它的好坏影响着整个Spark的性能。
影响一个Spark作业性能的因素,主要还是代码开发、资源参数以及数据倾斜,shuffle调优只能在整个Spark的性能调优中占到一小部分。

猜你喜欢

转载自blog.csdn.net/Thomson617/article/details/87883060