Spark on yarn 的内存分配问题

问题描述

在测试 spark on yarn 时,发现一些内存分配上的问题,具体如下。

在 $SPARK_HOME/conf/spark-env.sh 中配置如下参数:

SPARK_EXECUTOR_INSTANCES=4 在 yarn 集群中启动的 executor 进程数

SPARK_EXECUTOR_MEMORY=2G 为每个 executor 进程分配的内存大小

SPARK_DRIVER_MEMORY=1G 为 spark-driver 进程分配的内存大小

执行 $SPARK_HOME/bin/spark-sql –master yarn,按 yarn-client 模式启动 spark-sql 交互命令行(即 driver 程序运行在本地,而非 yarn 的 container 中),日志显示的关于 AppMaster 和 Executor 的内存信息如下:

日志显示,AppMaster 的内存是 896MB,其中包含了 384MB 的 memoryOverhead;启动了 5 个 executor,第一个的可用内存是 530.3MB,其余每个 Executor 的可用内存是 1060.3MB。

到 yarnUI 看下资源使用情况,共启动了 5 个 container,占用内存 13G,其中一台 NodeManager 启动了 2 个 container,占用内存 4G(1 个 AppMaster 占 1G、另一个占 3G),另外 3 台各启了 1 个 container,每个占用 3G 内存。

再到 sparkUI 看下 executors 的情况,这里有 5 个 executor,其中 driver 是运行在执行 spark-sql 命令的本地服务器上,另外 4 个是运行在 yarn 集群中。Driver 的可用 storage memory 为 530.3MB,另外 4 个都是 1060.3MB(与日志信息一致)。

那么问题来了:

  1. Yarn 为 container 分配的最小内存由 yarn.scheduler.minimum-allocation-mb 参数决定,默认是 1G,从 yarnUI 中看确实如此,可为何 spark 的日志里显示 AppMaster 的实际内存是 896-384=512MB 呢?384MB 是怎么算出来的?

  2. spark 配置文件里指定了每个 executor 的内存为 2G,为何日志和 sparkUI 上显示的是 1060.3MB?

  3. driver 的内存配置为 1G,为何 sparkUI 里显示的是 530.3MB 呢?

  4. 为何 yarn 中每个 container 分配的内存是 3G,而不是 executor 需要的 2G 呢?

问题解析

进过一番调研,发现这里有些概念容易混淆,整理如下,序号对应上面的问题:

(1) spark 的 yarn-client 向 ResourceManager 申请提交作业 / 启动 AppMaster 时,会判断是否是集群模式,如果是集群模式,则 AppMaster 的内存大小与 driver 内存大小一致,否则由 spark.yarn.am.memory 决定,这个参数的默认值是 512MB。我们使用的是 yarn-client 模式,所以实际内存是 512MB。

384MB 是 spark-client 为 appMaster 额外申请的内存,计算方法如下:

即,默认从参数读取(集群模式从 spark.yarn.driver.memoryOverhead 参数读,否则从 spark.yarn.am.memoryOverhead 参数读),若没配此参数,则从 AppMaster 的内存 * 一定系数和默认最小 overhead 中取较大值。

在 spark-1.4.1 版本中,MEMORY_OVERHEAD_FACTOR 的默认值为 0.10(之前是 0.07),MEMORY_OVERHEAD_MIN 默认为 384,我们没有指定 spark.yarn.driver.memoryOverhead 和 spark.yarn.am.memoryOverhead,而 amMemory=512M(由 spark.yarn.am.memory 决定),因此 memoryOverhead 为 max (512*0.10, 384)=384MB。

Executor 的 memoryOverhead 计算方法与此一样,只是不区分是否集群模式,都默认由 spark.yarn.executor.memoryOverhead 配置。

(2) 日志和 sparkUI 上显示的是 executor 内部用于缓存计算结果的内存空间,并不是 executor 所拥有的全部内存。这部分内存是由以下公式计算:

Runtime.getRuntime.maxMemory 按 2048MB 算,storage memory 大小为 1105.92MB,sparkUI 显示的略小于此值,是正常的。

(3) 与上述第 2 点一样,storage memory 的大小略小于 10240.90.6=552.96MB

(4) 前面提到 spark 会为 container 额外申请一部分内存(memoryOverhead),因此,实际为 container 提交申请的内存大小是 2048 + max (2048*0.10, 384) = 2432MB,而 yarn 在做资源分配时会做资源规整化,即应用程序申请的资源量一定是最小可申请资源量的整数倍(向上取整),最小可申请内存量由 yarn.scheduler.minimum-allocation-mb 指定,因此,会为 container 分配 3G 内存。

验证

为了验证上述规则,继续修改配置参数:

SPARK_EXECUTOR_INSTANCES=4 在 yarn 集群中启动的 executor 进程数

SPARK_EXECUTOR_MEMORY=4G 为每个 executor 进程分配的内存大小

SPARK_DRIVER_MEMORY=3G 为 spark-driver 进程分配的内存大小

并在启动 spark-sql 时指定 spark.yarn.am.memory 参数:

bin/spark-sql –master yarn –conf spark.yarn.am.memory=1024m

再看日志信息:

yarnUI 状态:

sparkUI 的 executors 信息:

可见,AppMaster 的实际内存为 1024M(1408-384),而其在 yarn 中的 container 内存大小为 2G(1408 大于 1G,yarn 按资源规整化原则为其分配 2G)。

同理,driver 的 storage memory 空间为 3G*0.9*0.6=1.62G,executor 的 storage memory 空间为 4G*0.9*0.6=2.16G,executor 所在 container 占用 5G 内存(4096+max (4096*0.10,384)= 4505.6,大于 4G, yarn 按资源规整化原则为其分配 5G)。

Yarn 集群的内存总占用空间为 2+5*4=22G。