今天在测试Spark集群的时候,因为我需要看下自己开发的RPC接口对原生的Spark参数支持是否完整,所以需要对各种参数做测试,中间碰到一个问题是:

Spark context stopped while waiting for backend

看了原因是因为我设置的内存参数过高,而机器实际的物理内存不足,因此无法正常拉起一个Spark作业,Yarn的nodemanager节点会对提交上来的任务(本例为spark on yarn)进行内存可分配性检查,涉及到对物理内存和虚拟内存的检查,当机器内存性能不太高时,无法通过内存检查。

于是我打算关闭此选项,来通过不预检内存来尝试启动程序的目的(受限于物理内存的制约,可能会失败)。

关闭的方法是在yarn-site.xml中添加如下配置项,并重启yarn,程序在 “–driver-memory 24G –executor-memory 6G “的参数下已可以成功运行。

<property>
<name>yarn.nodemanager.pmem-check-enabled</name>
<value>false</value>
</property>
<property>
<name>yarn.nodemanager.vmem-check-enabled</name>
<value>false</value>
</property>

这里需要了解一些Spark的向Yarn申请资源的计算规则,也就是知道基于参数,Yarn分配多少资源给Spark作业,

Spark任务会根据自己的executors的个数向yarn申请对应个数的container来跑任务,每个executor相当于一个JVM进程。
除了跑任务的container,Yarn会额外给每一个spark任务分配一个container用来跑ApplicationMaster进程,整个进程用来调控spark任务。
对于Spark任务,ApplicationMaster内存大小由上面的spark.driver.memory控制。

Yarn两个默认内存参数

  • yarn.scheduler.minimum-allocation-mb 每个container的最小内存值
  • yarn.scheduler.maximum-allocation-mb 每个container的最大内存值

也就是说:

  • 如果你设置的spark.executor.memory参数值比yarn.scheduler.minimum-allocation-mb小,那yarn生成的container内存大小会默认会使用yarn.scheduler.minimum-allocation-mb的值。
  • 如果你设置的spark.executor.memory参数值比yarn.scheduler.maximum-allocation-mb大,那yarn生成的container内存大小默认会使用yarn.scheduler.maximum-allocation-mb的值。
  • 还有个隐藏的条件,yarn生成的container内存大小必须是yarn.scheduler.minimum-allocation-mb值的整数倍

Spark on yarn有一个memoryOverhead的概念,是为了防止内存溢出额外设置的一个值,可以用spark.yarn.executor.memoryOverhead参数手动设置,如果没有设置,默认memoryOverhead的大小由以下公式计算:
memoryOverhead = max(spark.executor.memory * 0.07,384)

// Executor memory in MB.
protected val executorMemory = args.executorMemory
// Additional memory overhead.
protected val memoryOverhead: Int = sparkConf.getInt("spark.yarn.executor.memoryOverhead",
math.max((MEMORY_OVERHEAD_FACTOR * executorMemory).toInt, MEMORY_OVERHEAD_MIN))
// Number of cores per executor.
protected val executorCores = args.executorCores
// Resource capability requested for each executors
private val resource = Resource.newInstance(executorMemory + memoryOverhead, executorCores)

Yarn对Spark任务内存的申请计算方式

举个例子:

我们线上yarn的yarn.scheduler.minimum-allocation-mb设置的是2G
spark.executor.instances: 56
spark.executor.memory :2G
spark.driver.memory:5G

按照这部分参数,计算出来申请的内存大小应该是:

56 * 2G + 5G = 117G

但是任务提交之后,从yarn的资源管理界面看到申请的内存大小为:

235520M = 230G

几乎是理论值的两倍。

背后的计算逻辑是:

executor使用的内存计算

memoryOverhead = max(spark.executor.memory * 0.07,384) = max(2 * 1024 * 0.07,384) = 384M

则一个executor实际要使用的内存大小为:

totalMemory = spark.executor.memory + memoryOverhead = 2 * 1024 + 384 = 2432M

又因为yarn.scheduler.minimum-allocation-mb = 2G = 2048M

由yarn的container内存大小的性质可知,container内存大小必须是yarn.scheduler.minimum-allocation-mb的整数倍,我们现在需要的内存大小是 2432M,而yarn.scheduler.minimum-allocation-mb是2048M。
那么比2432M大且是2048M整数倍的最小值是4096M=4G,所以实际上yarn为spark申请的每个container的内存大小并不是我们设置的2G,而是4G

这样,我们有56个executor,也就是会申请56个container,则消耗的总内存是 56 * 4G = 224G

driver的内存计算

计算方式和上面一样:

memoryOverhead = max(spark.driver.memory * 0.07,384) = max(5 * 1024 * 0.07,384) = 384M

则driver实际要使用的内存大小为:

totalMemory = spark.driver.memory + memoryOverhead = 5 * 1024 + 384 = 5504M

同理,比5504M大且是2048M的整数倍的最小值是6144M=6G 故driver实际申请的内存大小是6G

所以整个spark任务最终申请的总内存大小为 224G + 6G = 230G。


扫码手机观看或分享: