参考文章 :
1.大数据技术之_19_Spark学习_07_Spark 性能调优 + 数据倾斜调优 + 运行资源调优 + 程序开发调优 + Shuffle 调优 + GC 调优 + Spark 企业应用案例
https://blog.csdn.net/u012990179/article/details/89812136
2.Spark 官方文档,针对于Spark 表现性能的调优
http://spark.apache.org/docs/latest/configuration.html#shuffle-behavior
之前一直没有出相关的文章,这次整理一下。希望你看了这篇文章,作业的 task 不是200 个!!!
首先 Shuffle 相关的参数与理解整个Shuffle 流程是密不可分的,建议在看这篇文章,如果你对整个Shuffle 流程还没有一个直观的认识,可以看下这篇文章。
https://blog.csdn.net/u010003835/article/details/106654789
基本资源调整
首先 ,Spark Shuffle 调优只是 Spark 中的一部分。并不是说通过调优,Spark 任务就会如何的快 。重要的是资源!!!
Spark 的资源在于以下几个参数, 提交作业申请的 executor-num , executor-core, executor-memory 以及 driver-memory
- num-executors
– YARN-only
参数说明:该参数用于设置 Spark 作业总共要用多少个 Executor 进程来执行。Driver 在向 YARN 集群管理器申请资源时,YARN 集群管理器会尽可能按照你的设置来在集群的各个工作节点上,启动相应数量的 Executor 进程。这个参数非常之重要,如果不设置的话,默认只会给你启动少量的 Executor 进程,此时你的 Spark 作业的运行速度是非常慢的。
参数调优建议:每个 Spark 作业的运行一般设置 50~100 个左右的 Executor 进程比较合适,设置太少或太多的 Executor 进程都不好。设置的太少,无法充分利用集群资源;设置的太多的话,大部分队列可能无法给予充分的资源。
- executor-memory
参数说明:该参数用于设置每个 Executor 进程的内存。Executor 内存的大小,很多时候直接决定了 Spark 作业的性能,而且跟常见的 JVM OOM 异常,也有直接的关联。
参数调优建议:每个 Executor 进程的内存设置 4G~8G 较为合适。但是这只是一个参考值,具体的设置还是得根据不同部门的资源队列来定。可以看看自己团队的资源队列的最大内存限制是多少,num-executors * executor-memory,是不能超过队列的最大内存量的。此外,如果你是跟团队里其他人共享这个资源队列,那么申请的内存量最好不要超过资源队列最大总内存的 1/3~1/2,避免你自己的 Spark 作业占用了队列所有的资源,导致别的同学的作业无法运行。
- executor-cores
– Spark standalone and YARN only
参数说明:该参数用于设置每个 Executor 进程的 CPU core 数量。这个参数决定了每个 Executor 进程并行执行 task 线程的能力。因为每个 CPU core 同一时间只能执行一个 task 线程,因此每个 Executor 进程的 CPU core 数量越多,越能够快速地执行完分配给自己的所有 task 线程。
参数调优建议:Executor 的 CPU core 数量设置为 2~4 个较为合适。同样得根据不同部门的资源队列来定,可以看看自己的资源队列的最大 CPU core 限制是多少,再依据设置的 Executor 数量,来决定每个 Executor 进程可以分配到几个 CPU core。同样建议,如果是跟他人共享这个队列,那么 num-executors * executor-cores 不要超过队列总 CPU core 的 1/3~1/2 左右比较合适,也是避免影响其他同学的作业运行。
- spark.default.parallelism
注意: 这个参数一定要做调整,否则很可能出现 task 没数据的情况 !!! 有些同学讲调优,但是这个参数都不做调整 。。。
参数说明:该参数用于设置每个 stage 的默认 task 数量。这个参数极为重要,如果不设置可能会直接影响你的 Spark 作业性能。
参数调优建议:Spark 作业的默认 task 数量为 500~1000 个较为合适。很多同学常犯的一个错误就是不去设置这个参数,那么此时就会导致 Spark 自己根据底层 HDFS 的 block 数量来设置 task 的数量,默认是一个 HDFS block 对应一个 task。通常来说,Spark 默认设置的数量是偏少的(比如就几十个 task),如果 task 数量偏少的话,就会导致你前面设置好的 Executor 的参数都前功尽弃。试想一下,无论你的 Executor 进程有多少个,内存和 CPU 有多大,但是 task 只有 1 个或者 10 个,那么 90% 的 Executor 进程可能根本就没有 task 执行,也就是白白浪费了资源!因此 Spark 官网建议的设置原则是,设置该参数为 num-executors * executor-cores 的 2~3 倍 较为合适,比如 Executor 的总 CPU core 数量为 300 个,那么设置 1000 个 task 是可以的,此时可以充分地利用 Spark 集群的资源。
- spark.storage.memoryFraction (deprecated)
参数说明:该参数用于设置 RDD 持久化数据在 Executor 内存中能占的比例,默认是 0.6。也就是说,默认 Executor 60% 的内存,可以用来保存持久化的 RDD 数据。根据你选择的不同的持久化策略,如果内存不够时,可能数据就不会持久化,或者数据会写入磁盘。
参数调优建议:如果 Spark 作业中,有较多的 RDD 持久化操作,该参数的值可以适当提高一些,保证持久化的数据能够容纳在内存中。避免内存不够缓存所有的数据,导致数据只能写入磁盘中,降低了性能。但是如果 Spark 作业中的 shuffle 类操作比较多,而持久化操作比较少,那么这个参数的值适当降低一些比较合适。此外,如果发现作业由于频繁的 gc 导致运行缓慢(通过 spark web ui 可以观察到作业的 gc 消耗),意味着 task 执行用户代码的内存不够用,那么同样建议调低这个参数的值。
- spark.shuffle.memoryFraction (deprecated)
参数说明:该参数用于设置 shuffle 过程中一个 task 拉取到上个 stage 的 task 的输出后,进行聚合操作时能够使用的 Executor 内存的比例,默认是 0.2。也就是说,Executor 默认只有 20% 的内存用来进行该操作。shuffle 操作在进行聚合时,如果发现使用的内存超出了这个 20% 的限制,那么多余的数据就会溢写到磁盘文件中去,此时就会极大地降低性能。
参数调优建议:如果 Spark 作业中的 RDD 持久化操作较少,shuffle 操作较多时,建议降低持久化操作的内存占比,提高 shuffle 操作的内存占比比例,避免 shuffle 过程中数据过多时内存不够用,必须溢写到磁盘上,降低了性能。此外,如果发现作业由于频繁的 g c导致运行缓慢,意味着 task 执行用户代码的内存不够用,那么同样建议调低这个参数的值。
额外 :
1)Spark 目前也支持了 Off-heap memory, 默认是不开启的,Off-heap 绕过了JVM 的垃圾回收机制,排除了 Full GC 影响。
2) spark.storage.memoryFraction (deprecated) 与 spark.shuffle.memoryFraction (deprecated) 是老版本 Spark 1.5 及之前的版本的固定内存参数进行设置的。如果让这些内存参数生效 spark.memory.useLegacyMode 需要设置为 true
1.6+ 的浮动内存管理版本,初始比例由 spark.memory.fraction 进行确定, Fraction of (heap space - 300MB) used for execution (中间数据含Shuffle 数据) and storage.
最新文档
http://spark.apache.org/docs/latest/configuration.html#memory-management
小结 :
资源参数的调优,没有一个固定的值,需要同学们根据自己的实际情况(包括 Spark 作业中的 shuffle 操作数量、RDD 持久化操作数量以及 spark web ui 中显示的作业 gc 情况),同时参考本篇文章中给出的原理以及调优建议,合理地设置上述参数。
一个 CPU core 同一时间只能执行一个线程。而每个 Executor 进程上分配到的多个 task,都是以每个 task 一条线程的方式,多线程并发运行的。
一个应用提交的时候设置多大的内存?设置多少 Core?设置几个 Executor?
参考作业资源申请:
以下是一份 spark-submit 命令的示例,大家可以参考一下,并根据自己的实际情况进行调节
./bin/spark-submit \
--master yarn-cluster \
--num-executors 100 \
--executor-memory 6G \
--executor-cores 4 \
--driver-memory 1G \
--conf spark.default.parallelism=1000 \
--conf spark.storage.memoryFraction=0.5 \
--conf spark.shuffle.memoryFraction=0.3 \
Spark Shuffle 参数调整
下面我们看下 Spark Shuffle 的一些调整参数
以下是 Shuffle 过程中的一些主要参数,这里详细讲解了各个参数的功能、默认值以及基于实践经验给出的调优建议。
完整的参数说明 :
http://spark.apache.org/docs/latest/configuration.html#shuffle-behavior
- spark.shuffle.file.buffer
默认值:32k
参数说明:该参数用于设置 shuffle write task 的 BufferedOutputStream 的 buffer 缓冲大小。将数据写到磁盘文件之前,会先写入 buffer 缓冲中,待缓冲写满之后,才会溢写到磁盘。
调优建议:如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如 64k),从而减少 shuffle write 过程中溢写磁盘文件的次数,也就可以减少磁盘 IO 次数,进而提升性能。在实践中发现,合理调节该参数,性能会有 1%~5% 的提升。
- spark.reducer.maxSizeInFlight
默认值:48m
参数说明:该参数用于设置 shuffle read task 的 buffer 缓冲大小,而这个 buffer 缓冲决定了每次能够拉取多少数据。
调优建议:如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如 96m),从而减少拉取数据的次数,也就可以减少网络传输的次数,进而提升性能。在实践中发现,合理调节该参数,性能会有 1%~5% 的提升。
- spark.shuffle.sort.bypassMergeThreshold
默认值:200
参数说明:当 ShuffleManager 为 SortShuffleManager 时,如果 shuffle read task 的数量小于这个阈值(默认是 200),则 shuffle write 过程中不会进行排序操作,而是直接按照未经优化的 HashShuffleManager 的方式去写数据,但是最后会将每个 task 产生的所有临时磁盘文件都合并成一个文件,并会创建单独的索引文件。
调优建议:当你使用 SortShuffleManager 时,如果的确不需要排序操作,那么建议将这个参数调大一些,大于 shuffle read task 的数量。那么此时就会自动启用 bypass 机制,map-side 就不会进行排序了,减少了排序的性能开销。但是这种方式下,依然会产生大量的磁盘文件,因此 shuffle write 性能有待提高。
- spark.shuffle.io.maxRetries
默认值:3
参数说明:shuffle read task 从 shuffle write task 所在节点拉取属于自己的数据时,如果因为网络异常导致拉取失败,是会自动进行重试的。该参数就代表了可以重试的最大次数。如果在指定次数之内拉取还是没有成功,就可能会导致作业执行失败。
调优建议:对于那些包含了特别耗时的 shuffle 操作的作业,建议增加重试最大次数(比如 60 次),以避免由于 JVM 的 full gc 或者网络不稳定等因素导致的数据拉取失败。在实践中发现,对于针对超大数据量(数十亿~上百亿)的 shuffle 过程,调节该参数可以大幅度提升稳定性。
spark.shuffle.io.retryWait
默认值:5s
参数说明:具体解释同上,该参数代表了每次重试拉取数据的等待间隔,默认是 5s。
调优建议:建议加大间隔时长(比如 60s),以增加 shuffle 操作的稳定性。
spark.shuffle.manager
废弃 Spark2.0 +移除了 hash
Spark 2.4.5 已经移除了 Hash Shuffle !!!
默认值: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。
频繁 GC 或者 OOM
针对这种情况,首先要确定现象是发生在 Driver 端还是在 Executor 端,然后在分别处理。
Driver 端:通常由于计算过大的结果集被回收到 Driver 端导致,需要调大 Driver 端的内存解决,或者进一步减少结果集的数量。
Executor 端:
(1)以外部数据作为输入的 Stage:这类 Stage 中出现 GC 通常是因为在 Map 侧进行 map-side-combine 时,由于 group 过多引起的。解决方法可以增加 partition 的数量(即 task 的数量)来减少每个 task 要处理的数据,来减少 GC 的可能性。
(2)以 shuffle 作为输入的 Stage:这类 Stage 中出现 GC 的通常原因也是和 shuffle 有关,常见原因是某一个或多个 group 的数据过多,也就是所谓的数据倾斜,最简单的办法就是增加 shuffle 的 task 数量,比如在 SparkSQL 中设置 SET spark.sql.shuffle.partitions=400,如果调大 shuffle 的 task 无法解决问题,说明你的数据倾斜很严重,某一个 group 的数据远远大于其他的 group,需要你在业务逻辑上进行调整,预先针对较大的 group 做单独处理。