Spark资源调优手册
前言
性能调优该如何去做?面对成百上千的业务代码、近百个spark 配置项该如何入手?这里帮大家简单的归纳了下与性能调优相关的配置项,并从如何去评估资源出发,让我们在进行资源设置的时候有所依据
1.1 资源预估
调优的切入点是瓶颈,定位瓶颈之一的方法就是从硬件的角度出发,CPU,内存,磁盘等。最终调优的收敛也是在于硬件资源的平衡。所以我们调优首先就要了解我们自己的集群节点,了解在当前集群环境下如何去合理的规划资源
1.1.1 如何结合集群环境合理规划资源
当前我们公司的数据平台的机器配置种类有四种。我这里使用96C/320G 的配置为例,再结合集群实际的配置参数,教会大家如何去评估自己提交的spark任务的资源是否合理,让我们提交参数有据可依。
<property> --可为容器分配的vcore数 当hardware-capabilities 为true 自动从硬件确定
<name>yarn.nodemanager.resource.cpu-vcores</name>
<value>-1</value>
<final>false</final>
<source>yarn-site.xml</source>
</property>
<property> --可以为容器分配的物理内存量200G
<name>yarn.nodemanager.resource.memory-mb</name>
<value>204800</value>
<final>false</final>
<source>yarn-site.xml</source>
</property>
<property> --每个容器请求的最大资源分配 --40G
<name>yarn.scheduler.maximum-allocation-mb</name>
<value>40960</value>
<final>false</final>
<source>yarn-site.xml</source>
</property>
<property> --每个容器请求的最小资源分配
<name>yarn.scheduler.minimum-allocation-mb</name>
<value>512</value>
<final>false</final>
<source>yarn-site.xml</source>
</property>
<property>
<name>yarn.nodemanager.resource.detect-hardware-capabilities</name>
<value>true</value>
<final>false</final>
<source>yarn-site.xml</source>
</property>
1.1.1.1 总体的可用资源评估
先评估整体可用executor 自由。先设定单个Executor核数,根据Yarn配置得出每个节点最多的Executor 数量。
公式: 每个节点的Executor数 = 容器可分配线程数/每个Executor 核数
单个节点的内存=每个节点的Yarn 内存/每个节点的Executor数
总Executor数=单节点数量*节点数。
1.1.1.2 资源推荐上限评估
介绍如何根据现有集群环境评估资源参数提交的上限,让我们能更合理的提交资源参数
1.1.1.2.1 executor-cores
每个executor的最大核数。根据经验实践,设定在2~6之间比较合理。
1.1.1.2.2 num-executors
每个node的executor数 = 单节点yarn总核数 / 每个executor的最大cpu核数。
根据上述配置文件(自动生成推荐)再结合实际Yarn 上的数值。
yarn.nodemanager.resource.cpu-vcores 大约为51 。若executor-cores 值为4的情况下,那么单台node的Executor 数为:
公式:单节点Executor数=51(yarn.nodemanager.resource.cpu-vcores)/4(executor-cores)=12。
所以理想情况下单台节点按上述配置最多启动12台Executor
1.1.2.2.3 executor-memory
公式:单Executor内存=yarn-nodemanager.resource.memory-mb / 每个节点的executor数量。
根据当前集群的配置参数,yarn 容器的memory 为200G,那么单个Executor 最大是内存约为16G(200G/12 ) 。
同时要注意yarn配置中每个容器允许的最大内存是否匹配(40G)。
1.2 性能调优
下面是一份Spark的配置手册,分们别类的诺列了与性能调优息息相关的配置,可以让你在寻找调优思路的时候迅速的定位可能会用到的配置项项目。
1.2.1 Executor数量
任务总并行度=Executor 数 x 每个Executor的Core 数
若开启动态资源分配,则可以根据需要适当的节省资源使用(建议不太熟悉的同学使用动态资源分配)
若不开启动态资源分配,需要通过 --num-executors 200 或 --conf spark.executor.instances=200 来手动指定固定的Executor 数。
1.1.2.1 动态分配模型
建议不要自己配置Executor个数,使用动态分配模式。开启动态资源分配可以优化资源使用。同时也要求开启external shuffle service,on yarn模式external shuffle service是嵌入到nodemanager中的,要求nodemanager在启动时最好多分配一些内存。
开启动态资源分配的参数如下
1.1.2.2 静态分配模型
在资源允许的情况下,增加 Executor 的个数可以提高执行 task 的并行度。比如有 4 个Executor,每个 Executor 有 2 个 CPU core,那么可以并行执行 8 个 task,如果将 Executor 的个数增加到 8 个( 资源允许的情况下), 那么可以并行执行 16 个 task, 此时的并行能力提升了一倍。
1.1.2.3 Executor 申请等待
当Executor 等待直接较长是可以考虑是否没有达到最小比例。
1.2.2 CPU分配
当以yarn cluster模式运行时,越大的driver cores会使当前程序的driver获取可能更多的CPU时间片,进而可以提升driver性能。
Executor cores 越大,那一个Executor(jvm)中可以同时运行的task数就越多(此值默认为1),若用户spark程序是线程安全的,可调大此值,但需注意executor memory也几乎需按照正比例调整。
比如有 4 个 Executor,每个 Executor 有 2 个CPU core,那么可以并行执行 8 个 task,如果将每个 Executor 的 CPU core 个数增加到 4 个( 资源允许的情况下),那么可以并行执行 16个 task, 此时的并行能力提升了一倍。
我们需要明确cup与并行度,并行计算的关系才能够合理的去设置。首先并行度的出发点是数据,它明确了数据划分的粒度,数据的粒度决定了分区的大小,分区的大小决定着每个计算任务的内存消耗。并行计算不同,它指的是在任意时刻整个集群能够同时计算的任务数量,如下面公式:
单个Executor 中并行计算任务数上=spark.executor.cores/spark.task.cpus
整个集群的并行计算任务数=单个Executor 中并行计算任务数 * 集群内Executor 数
注意:在队列有大量任务提交的情况下,建议少点,以免影响其他用户提交的任务因申请不到cpu资源而卡主
1.2.3 内存分配
一般情况下我们不会调整driver,driver 不负责具体内存的运算,除非调用collect 类算子。所以我们更多的要关注Executor 内存。增加每个 Executor 的内存量以后,可以缓存更多的数据减少磁盘IO,可以为shuffle提供更多的内存减少磁盘IO,可以为task 执行提供更多的内存避免频繁的GC.
spark内存=堆内内存+堆外内存
1.2.3.1 内存设置时要考虑的因子
- 自己使用的executor-memory * num-executor所使用的资源不能超过所提交队列的阈值
- 在队列资源共用的模式下,所申请的资源还要更小,以免申请不到资源或者阻塞其他用户的任务
- 用户申请的executor-momory不能超过yarn设置的最大值,当前容器设置的最大值为40g;
4)如何平衡堆外与堆内。堆外内存相比于堆内的优势在于,更精确的内存占用统计和不需要要垃圾回收机制,以及不需要序列化和反序列化。但并不是什么场合都适合。如果处理的数据集比较扁平,且字段多为定长数据类型,就更多的使用堆外。相反如果数据集很复杂,嵌套结构或变长字段很多采用jvm堆内内存会更稳妥。
1.2.3.2 更加精确的分配内存(使用堆内内存)
每个任务使用的资源不可能一样,每个集群的性能也不可能一样。如果合理的规划资源,我们要先了解executor 的堆内内存模型:
1.2.3.1 Executor内存预估
● 估算Other内存 = 自定义数据结构每个Executor核数
● 估算Storage内存 = 广播变量 + cache/Executor数量
● 估算Executor内存 = 每个Executor核数 * (数据集大小/并行度)
根据上面公式推算一个Executor 内存:
比如一个要shuffle的数据集100G,并行度spark sql shuffle 默认200。那每个task处理的数据量为500M。假如我们的Executor 是4core,那一个Executor 执行内存需要的资源为5004=2G。
那么storage 也为2G,统一内存大约4G,可以内存大约6.7G。最终 spark.executor.memory 的内存约7G(6.7+0.3)。
1.2.3.2 Executor调整内存比例
一般情况下,各个区域的内存比例保持默认值即可。如果需要更加精确的控制内存分配,可以按照如公式:
- Storage堆内内存=(spark.executor.memory–300MB)spark.memory.fractionspark.memory.storageFraction
- Execution堆内内存=(spark.executor.memory–300MB)spark.memory.fraction(1-spark.memory.storageFraction)
存储内存与执行内存的平衡在于你的计算任务是缓存密集型,如机器学习训练任务。还是非缓存密集型。
还需要注意的是大量的缓存会引入GC,可以通过缓存压缩参数来避免。
1.2.3.2 使用堆外内存
讲到堆外内存,就必须去提一个东西,那就是去yarn申请资源的单位,容器。Spark on yarn模式,一个容器到底申请多少内存资源。
一个容器最多可以申请多大资源,是由yarn参数yarn.scheduler.maximum-allocation-mb决定, 需要满足:
spark.executor.memoryOverhead + spark.executor.memory + spark.memory.offHeap.size
≤ yarn.scheduler.maximum-allocation-mb
参数解释:
● spark.executor.memory:提交任务时指定的堆内内存。
● spark.executor.memoryOverhead:堆外内存参数,内存额外开销。
默认开启,默认值为spark.executor.memory*0.1并且会与最小值384mb做对比,取最大值。所以spark on yarn任务堆内内存申请1个g,而实际去yarn申请的内存大于1个g的原因。
● spark.memory.offHeap.size:堆外内存参数,spark中默认关闭,需要将spark.memory.enable.offheap.enable参数设置为true。
注意:很多网上资料说spark.executor.memoryOverhead包含spark.memory.offHeap.size,这是由版本区别的,仅限于spark3.0之前的版本。3.0之后就发生改变,实际去yarn申请的内存资源由三个参数相加。
使用堆外内存可以减轻垃圾回收的工作,也加快了复制的速度。
当需要缓存非常大的数据量时,虚拟机将承受非常大的GC压力,因为虚拟机必须检查每个对象是否可以收集并必须访问所有内存页。本地缓存是最快的,但会给虚拟机带来GC压力,所以,当你需要处理非常多GB的数据量时可以考虑使用堆外内存来进行优化,因为这不会给Java垃圾收集器带来任何压力。让JAVA GC为应用程序完成工作,缓存操作交给堆外。
1.2.4 Shuffle 并行度
1.2.5 gc调优
使用G1垃圾回收器可减少gc暂停时间,提升任务稳定性