一、先搞懂:为什么container日志找不到有效线索?

很多做大数据开发的人都遇过这种糟心的事:提交的Spark作业到YARN集群里跑,反反复复失败,查container日志却只能看到几句“Container killed by YARN”,要么是空泛的错误提示,要么是内容被截断,根本找不到具体原因。这主要是因为YARN的container日志本身有两个特点:一是默认保留时间短(只有24小时),如果作业失败后没及时查,日志就被清理了;二是很多关键报错信息(比如到底是物理内存不够还是虚拟内存超限)会被分散到多个日志文件里,普通开发者只会找stdout,忽略了stderr或者nodemanager的系统日志,自然找不到头绪。我之前就踩过这个坑:公司的日常报表Spark作业,连续三天凌晨定时失败,查container日志全是“被YARN杀死”,没说为啥,最后折腾了半天才发现是虚拟内存超限,不是物理内存的问题。

1.1 别盲目调大内存,先分清楚“内存类型”

很多人遇到作业失败的第一反应就是调大executor内存,结果越调越乱,甚至把集群其他作业也拖垮。其实Spark作业在YARN上被Kill,90%的原因和内存有关,但这内存分两种:物理内存(你可以理解成家里的餐桌,只能放热乎的、正在用的东西,速度快)和虚拟内存(相当于阳台的空地,餐桌不够放时,把暂时不用的东西挪到这里,速度慢,是硬盘空间虚拟出来的)。两种内存的排查逻辑完全不一样,先搞懂这个,才不会瞎折腾。

二、核心基础:物理内存和虚拟内存的通俗解释

不用记那些拗口的专业定义,就用生活例子说:

  • 物理内存:YARN给container分配的“直接能用的内存额度”,比如你给executor设了8G内存,就是物理内存最多用8G,超过的话会直接被Kill,这时候日志会直接说“物理内存超限”。
  • 虚拟内存:YARN额外给container的“缓冲额度”,一般是物理内存的1.1-2倍(看YARN配置),用来放偶尔超出物理内存的临时数据,但如果虚拟内存也用完了,也会被Kill,这时候日志不会直接提虚拟内存,只会说“container被kill”。

举个真实场景:如果你的Spark executor物理内存设为8G,YARN默认虚拟内存比例是2.1,那虚拟内存上限就是8G×2.1=16.8G,作业用到17G虚拟内存时,就会被YARN杀掉,这时候你如果只看物理内存,会发现没超,根本找不到问题。

三、排查逻辑:从物理内存到虚拟内存的一步步验证

3.1 第一步:先确认YARN和Spark的内存配置对不对

先别急着查日志,先看“资源匹配度”,很多失败都是因为配置不对应,这一步用Shell命令就能搞定,不用找复杂的配置文件。

# 先看当前YARN集群给应用分配的队列总内存,这里用默认队列举例
yarn queue -status default | grep -i memory

注释:返回结果里的totalMemory是队列允许的最大物理内存,usedMemory是已经用的,先确认作业要跑的资源有没有超队列上限。

# 再看Spark作业提交时的executor内存配置,比如作业ID是application_1620000000000_0001
spark-submit --properties-file your-spark-conf.conf --dry-run | grep "spark.executor.memory"

注释:比如查到executor设的是8G,而队列总内存只有6G,那肯定超了,这时候直接调小executor内存到5G就行,不用纠结其他问题。

3.2 第二步:排查物理内存超限的情况

如果配置没超,那大概率是物理内存真的不够,这时候要找container的OOM(内存不足)日志,重点看stderr文件,因为stdout不会输出系统级的报错。

# 查看指定container的详细错误日志,这里container ID是container_1620000000000_0001_01_000001
yarn logs -applicationId application_1620000000000_0001 -containerId container_1620000000000_0001_01_000001

注释:如果日志里出现“physical memory exceeded”(物理内存超限)或者“OOM killer triggered”(内存杀手触发),那就是物理内存的问题,这时候要么调大executor内存,要么减少executor数量,或者优化作业的代码(比如减少Shuffle数据量)。

3.3 第三步:如果物理内存没问题,查虚拟内存的问题

很多人最容易忽略虚拟内存,这时候看YARN的nodemanager日志,里面会有虚拟内存的相关提示。虚拟内存的问题主要来自两个参数:Spark的spark.executor.memoryOverhead(堆外内存比例,默认0.1,也就是10%)和YARN的yarn.nodemanager.vmem-pmem-ratio(虚拟内存和物理内存的比值,默认2.1)。

# 查看虚拟内存的关键配置,先看YARN的比值
yarn getconf -confKey yarn.nodemanager.vmem-pmem-ratio

注释:如果返回的是2.1,说明虚拟内存上限是物理内存的2.1倍,这时候如果你的作业用到了很多堆外内存(比如Spark的Shuffle、外部库调用),比如executor是8G,虚拟内存上限是16.8G,作业用到17G就会被杀,这时候要么调大spark.executor.memoryOverhead(比如改成0.2,即20%),要么调大YARN的比值(不推荐,会降低集群安全性)。

# 再看Spark的堆外内存配置,从配置文件里找
grep "spark.executor.memoryOverhead" your-spark-conf.conf

注释:如果这个值太小,比如默认的0.1,而作业堆外内存占比超过10%,就会导致虚拟内存超限,这时候把它改成0.2或者根据实际作业调整即可。

3.4 第四步:确认Kill信号对应的内存类型

Linux系统里,被KILL的container会有Exit Code,通过Exit Code也能辅助判断:Exit Code为137(128+9)一般是物理内存不足被SIGKILL信号杀死;Exit Code为143(128+15)一般是虚拟内存超限被YARN杀死,再结合日志,100%能定位问题。

四、这个排查方法的应用场景、优缺点和注意事项

4.1 应用场景

这个排查逻辑适合几乎所有Spark on YARN的作业失败场景:比如日常定时报表作业波动导致的失败、Spark Streaming作业数据峰值时的container Kill、大数据集群资源紧张时多个作业抢内存的情况,不管是刚入门的开发者还是有经验的运维,都能快速上手。

4.2 技术优缺点

优点:逻辑简单,全是现成的命令和日志,不用改复杂配置,适合快速排查;缺点:不同版本的YARN和Spark参数名称可能不一样,比如有的YARN版本yarn.nodemanager.vmem-pmem-ratioyarn.nodemanager.vmem-check-ratio,需要对应版本调整,不然会查错。

4.3 注意事项

有几个关键点一定要注意:一是YARN的日志聚合一定要开,不然作业失败后日志会分散在各个nodemanager上,查起来麻烦;二是如果虚拟内存检查没开(yarn.nodemanager.vmem-check-enabled为false),就算虚拟内存超了也不会Kill作业,这时候要查nodemanager的系统进程内存;三是Spark动态分配的作业,内存会自动调整,排查时要结合动态分配的配置,不然看的是旧的内存数值,会误导。

五、总结

Spark作业在YARN上反复失败但container日志无头绪,本质是没搞清楚“物理内存和虚拟内存的区别”,也没找对排查的方向。不用上来就调大内存,先看YARN和Spark的内存配置匹配度,再分是物理内存还是虚拟内存的问题,一步步查日志和参数,就能快速定位问题,既不会浪费集群资源,也能解决实际的业务痛点。很多时候,作业失败不是因为资源不够,而是配置不对应,或者忽略了虚拟内存的限制,按这个逻辑走,大部分问题都能迎刃而解。