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(与日志信息一致)。
那么问题来了:
-
Yarn 为 container 分配的最小内存由 yarn.scheduler.minimum-allocation-mb 参数决定,默认是 1G,从 yarnUI 中看确实如此,可为何 spark 的日志里显示 AppMaster 的实际内存是 896-384=512MB 呢?384MB 是怎么算出来的?
-
spark 配置文件里指定了每个 executor 的内存为 2G,为何日志和 sparkUI 上显示的是 1060.3MB?
-
driver 的内存配置为 1G,为何 sparkUI 里显示的是 530.3MB 呢?
-
为何 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。