资源影响因素
Spark和Yarn管理的资源限制因素:
- CPU
- 内存
- 磁盘
- 网络I/O
Spark和Yarn管理的两个主要资源为CPU和内存,剩下不会主动管理,所以设置资源也是主要通过这两方面进行设置。
资源优化配置
在资源配置中,可以用过代码的设置sparkconf进行设置,或者在脚本里通过参数进行设置,脚本的优先级大于代码的优先级。YARN的相关属性是
# 控制每个节点上的容器使用的最大内存总和。 yarn.nodemanager.resource.memory-mb # 控制每个节点上的容器使用的最大内核总数。 yarn.nodemanager.resource.cpu-vcores
YARN请求的内存时,需要注意一下两点:
--executor-memory/spark.executor.memory
控制执行程序堆的大小,但是JVM也可以在堆外使用一些内存,例如,spark.yarn.executor.memoryOverhead 用于内联的String和直接字节缓冲区。该属性的值将添加到执行程序内存中,以确定每个执行程序对YARN的完整内存请求。默认为max(384,.07 * spark.executor.memory)。yarn.scheduler.minimum-allocation-mb
yarn.scheduler.increment-allocation-mb
在确定Spark执行程序的大小时要注意的最后几个问题:
- 应用程序主服务器是一个非执行程序容器,具有从YARN请求容器的特殊功能,它占用必须自己预算的资源。在yarn-client模式下,它默认为1024MB和一个vcore。在yarn-cluster模式下,应用程序主控程序运行驱动程序,因此使用和属性支持其资源通常很有用。
--driver-memory
--driver-cores
- 运行内存过多的执行程序通常会导致过多的垃圾回收延迟(当内存不足时会触发JVM回收内存)。对于单个执行器来说,64GB是一个不错的上限。
- executor遇到大量并发线程效率会降低。官方的建议是,每个executor最多可以设置五个task,以实现较高效率的写入吞吐量,因此最好将每个executor的vcores保持在该数量以下。
- 启动太多executor执行比较少任务的时候,会抛弃同个executor起多线程的效率,反而降低效率。例如,广播变量需要在每个executor上复制一次,因此太多executor将产生更多的副本成本。
脚本模板
./spark-submit \ --class 入口类 --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 \ jar包路径 参数
num-executors
参数说明:该参数用于设置Spark作业总共要用多少个Executor进程来执行。Driver在向YARN集群管理器申请资源时,YARN集群管理器会尽可能按照你的设置来在集群的各个工作节点上,启动相应数量的Executor进程。不设置默认值是2。
参数调优建议:根据从节点的数据量设置对应的数量,设置太多会让一台节点起多个executor,影响机器的性能,起太少会让节点空闲。
executor-memory
参数说明:该参数用于设置每个Executor进程的内存。Executor内存的大小,很多时候直接决定了Spark作业的性能,而且跟常见的JVM OOM异常,也有直接的关联。
参数调优建议:具体情况得根据不同部门的资源队列和程序部署的情况来决定,数据源大小也是一个参考点;但num-executors * executor-memory,是不能超过队列的最大内存量的。此外,如果资源是和别人共享的情况或者多程序的情况,那么申请的内存量最好不要超过资源队列最大总内存的1/3~1/2,避免你自己的Spark作业占用了队列所有的资源,导致别的同事的作业无法运行。
executor-cores
参数说明:该参数用于设置每个Executor进程的CPU core数量。这个参数决定了每个Executor进程并行执行task线程的能力。因为每个CPU core同一时间只能执行一个task线程,因此每个Executor进程的CPU core数量越多,越能够快速地执行完分配给自己的所有task线程。
参数调优建议:每个Executor同时执行5个task时HDFS的写的吞吐量是最好的。所以,建议将executor-cores
的值为3-5。这个限制也要根据资源情况来限定,例如资源队列里最大的CPU core数量,假设你的executor的内存没办法配置太大,那么executor-cores不能配置太多,不然会导致内存不够而引起内存溢出。
driver-memory
参数说明:该参数用于设置Driver进程的内存。
参数调优建议:Driver的内存通常来说不设置,或者设置1G左右应该就够了。但你的程序有使用collect算子将RDD的数据全部拉取到Driver上进行处理(或者是用map side join操作),那么必须确保Driver的内存足够大,否则会出现OOM内存溢出的问题。
spark.default.parallelism
参数说明:该参数用于设置每个stage的默认task数量,也可以认为是分区数。
参数调优建议:Spark官网的建议是,设置该参数为num-executors * executor-cores的2~3倍较为合适;如果不设置这个参数,那么会导致Spark自己根据底层HDFS的block数量来设置task的数量,默认是一个HDFS block对应一个task。通常来说,Spark默认设置的数量是偏少的(比如就几十个task),如果task数量偏少的话,就会导致你前面设置好的Executor的cpu闲置,浪费资源。
spark.storage.memoryFraction
参数说明:该参数用于设置RDD持久化数据在Executor内存中能占的比例,默认是0.6。也就是说,默认Executor 60%的内存,可以用来保存持久化的RDD数据。根据你选择的不同的持久化策略,如果内存不够时,可能数据就不会持久化,或者数据会写入磁盘。
参数调优建议:如果Spark作业中,有较多的RDD持久化操作,该参数的值可以适当提高一些,保证持久化的数据能够容纳在内存中。避免内存不够缓存所有的数据,导致数据只能写入磁盘中,降低了性能。但是如果Spark作业中的shuffle类操作比较多,而持久化操作比较少,那么这个参数的值适当降低一些比较合适。此外,如果发现作业由于频繁的gc导致运行缓慢(通过spark web ui可以观察到作业的gc耗时),意味着task执行用户代码的内存不够用,那么同样建议调低这个参数的值。
spark.shuffle.memoryFraction
参数说明:该参数用于设置shuffle过程中一个task拉取到上个stage的task的输出后,进行聚合操作时能够使用的Executor内存的比例,默认是0.2。也就是说,Executor默认只有20%的内存用来进行该操作。shuffle操作在进行聚合时,如果发现使用的内存超出了这个20%的限制,那么多余的数据就会溢写到磁盘文件中去,此时就会极大地降低性能。
参数调优建议:如果Spark作业中的RDD持久化操作较少,shuffle操作较多时,建议降低持久化操作的内存占比,提高shuffle操作的内存占比比例,避免shuffle过程中数据过多时内存不够用,必须溢写到磁盘上,降低了性能。此外,如果发现作业由于频繁的gc导致运行缓慢,意味着task执行用户代码的内存不够用,那么同样建议调低这个参数的值。
配置参数
假设集群服务器配置:
节点:10 核数:16/台 内存:64GB/台
配置一:使用较小的executors
这里起和核数相同的executor
--num-executors = 16 x 10 = 160 --executor-cores = 1 (one executor per core) --executor-memory = `mem-per-node/num-executors-per-node` = 64GB/16 = 4GB
由于每个executor只分配了一个核,我们将无法利用在同一个JVM中运行多个任务的优点。 此外,共享/缓存变量(如广播变量和累加器)将在节点的每个核心中复制16次。 最严重的就是,我们没有为Hadoop / Yarn守护程序进程留下足够的内存开销,我们还忘记了将ApplicationManagers运行所需要的开销加入计算。
配置二:使用较大的executors
每个executor都有16个核
--num-executors = 10 --executor-cores = 16 --executor-memory = `mem-per-node/num-executors-per-node` = 64GB/1 = 64GB
由于HDFS客户端遇到大量并发线程会出现一些bug,即HDFS吞吐量会受到影响。同时过大的内存分配也会导致过多的GC延迟。
配置三:使用优化的executors
--num-executors = 29 --executor-cores = 5 --executor-memory = 19GB
(1)给每个Executor分配5个core即executor-cores=5
,这样对HDFS的吞吐量会比较友好。
(2)为后台进程留一个core,则每个节点可用的core数是16 - 1 = 15
。所以集群总的可用core数是15 x 10 = 150
。
(3)每个节点上的Executor数就是 15 / 5 = 3
,集群总的可用的Executor数就是 3 * 10 = 30
。为ApplicationManager
留一个Executor,则num-executors=29
。
(4)每个节点上每个Executor可分配的内存是 (64GB-1GB) / 3 = 21GB
(减去的1GB是留给后台程序用),除去MemoryOverHead=max(384MB, 7% * 21GB)=2GB
,所以executor-memory=21GB - 2GB = 19GB
。
此方法既保证了在一个JVM实例里能同时执行task的优势,也保证了hdfs的吞吐量。
配置四:在配置三基础上再优化
按照方法三,每个Executor分配到的内存是19GB,假设10GB内存就够用了。那么此时我们可以将executor-cores
降低如降低为3,那么每个节点就可以有15 / 3 = 5
个Executor,那么总共可以获得的Executor数就是 (5 * 10) - 1 =49
,每个节点上每个Executor可分配的内存是(64GB-1GB) / 5 = 12GB
,除去MemoryOverHead=max(384MB, 7% * 12GB)=1GB
,所以executor-memory=12GB - 1GB = 11GB
所以最后的参数配置是
--num-executors = 49 --executor-cores = 3 --executor-memory = 11GB
这里只是做一个参考,具体的实际情况依照个人的资源情况,参考有数据量大小,部门的资源使用情况等。一切以实际为准,这里只是搬运整理下资源以便翻阅。
参考博客:https://www.cnblogs.com/arachis/p/spark_parameters.html http://www.dzjqx.cn/news/show-28638.html https://blog.cloudera.com/how-to-tune-your-apache-spark-jobs-part-2/