1. 问题现场还原:当Spark任务卡在ACCEPTED状态,你在YARN上到底看到了什么
“spark yarn模式提交任务不成功(application state: ACCEPTED)”——这句话不是一句模糊的报错,而是一张精准的故障快照。它背后映射的是一个非常典型的、高频发生的分布式计算阻塞场景:你的Spark应用已经成功向YARN ResourceManager注册,拿到了Application ID,也通过了资源调度器的初步准入审核,但此后就再无下文。ApplicationMaster(AM)迟迟无法启动,整个作业像被按下了暂停键,永远停在ACCEPTED这个中间态。
我第一次遇到这个问题是在某高校实验室部署教学集群时,学生提交一个简单的spark-shell --master yarn --deploy-mode client命令后,终端卡住不动,yarn application -list输出里赫然显示ACCEPTED,持续5分钟、10分钟、甚至30分钟后依然如此。当时第一反应是“集群没资源?”,但yarn node -list显示所有NodeManager都在线且有空闲内存;第二反应是“网络不通?”,但telnet <rm-host> 8032能通;第三反应是“权限问题?”,可HDFS目录权限、YARN队列ACL都检查过了……最后花了整整两天时间,才定位到真正原因:YARN的NodeManager配置中yarn.nodemanager.resource.memory-mb值设得比物理内存还大,导致NodeManager启动时自动降级为“unhealthy”,虽在线却不参与资源分配。这个细节,在官方文档里藏在“NodeManager Health Checker”小节末尾,连很多资深运维都容易忽略。
这个问题之所以值得深挖,是因为它表面看是“提交失败”,实则横跨Spark、YARN、Linux系统、JVM、网络、安全认证五大层面。它不抛出ERROR日志,不中断进程,不触发告警,却让整个数据流水线彻底失能。对数据工程师而言,这是最折磨人的故障类型——没有堆栈,没有异常,只有安静的ACCEPTED。而解决它的过程,本质上是一次对整个大数据运行时环境的系统性体检。
适合阅读本文的,不是刚学Spark API的新手,而是已经能写UDF、调优Shuffle、配置spark.sql.adaptive.enabled的中级以上实践者。你可能正面临:CI/CD流水线里某个Spark任务突然变慢、调度平台里一批作业集体挂起、或是生产环境凌晨三点收到ACCEPTED状态告警却无从下手。本文不讲Spark基础概念,不重复官网配置项列表,只聚焦于“ACCEPTED”这个状态背后的真实链路、可验证的排查路径、以及那些文档里不会写的硬核经验。
2. 核心机制拆解:ACCEPTED状态在YARN调度生命周期中的真实含义
2.1 YARN Application生命周期中的关键断点
要理解ACCEPTED为何成为“死锁点”,必须先厘清YARN Application从提交到运行的完整状态跃迁路径。这不是一个线性流程,而是一个带条件分支与超时重试的有限状态机(FSM)。ACCEPTED并非终点,而是ResourceManager(RM)完成资源准入决策后,向ApplicationMaster(AM)发放“开工许可证”的前一刻。
标准状态流转如下:
SUBMITTED → ACCEPTED → RUNNING → FINISHED / FAILED / KILLED其中,ACCEPTED到RUNNING的跃迁,是整个流程中最脆弱的一环。它要求三个独立子系统在毫秒级时间内完成协同:
- ResourceManager(RM):已确认该应用符合队列配额、ACL策略、标签匹配等准入规则,并为其分配了一个逻辑容器(Container ID),但此容器尚未实际在任何NodeManager上启动;
- ApplicationMaster(AM):作为Spark应用的“总指挥”,其JAR包需被下载、解压、JVM启动、初始化完成,并向RM注册心跳;
- NodeManager(NM):需接收RM下发的容器启动指令,拉取AM的JAR包(通常来自HDFS或本地缓存),启动JVM进程,并将AM的RPC地址上报给RM。
ACCEPTED状态,就是RM已发出指令、但尚未收到任一NM成功启动AM容器的确认消息时的“等待态”。它本身不表示错误,而是一个超时等待窗口的起点。YARN默认的yarn.resourcemanager.am.max-attempts为2次,yarn.resourcemanager.scheduler.maximum-allocation-mb等参数共同决定了这个窗口的宽度。
提示:ACCEPTED状态的持续时间,由
yarn.resourcemanager.am.wait-time-ms参数控制(默认30秒),超过此时间未收到AM注册,RM会主动将状态置为FAILED。但很多集群未显式配置此参数,导致ACCEPTED无限期悬挂。
2.2 Spark on YARN特有的AM启动依赖链
Spark的AM启动比MapReduce更复杂,因为它不是原生YARN组件,而是通过spark-yarn模块桥接。这引入了额外的依赖环节,每一个都可能成为ACCEPTED的“绊脚石”:
依赖包分发路径:Spark客户端提交时,会将
spark-assembly.jar、spark-examples.jar及用户代码JAR打包上传至HDFS临时目录(如/user/spark/applicationHistory)。AM启动时,需从该路径下载所有JAR。若HDFS NameNode高负载、DataNode磁盘满、或HDFS ACL拒绝读取,AM将卡在“下载依赖”阶段,永不注册。本地化资源缓存机制:YARN的
Localizer服务负责将远程JAR缓存到NM本地磁盘(yarn.nodemanager.local-dirs)。若该目录所在分区空间不足(即使df -h显示有空间,但inodes耗尽)、权限错误(如chown -R yarn:yarn未执行)、或SELinux策略阻止写入,Localizer会静默失败,AM因无法获取JAR而无法启动。JVM启动参数冲突:Spark AM的JVM参数(如
--driver-java-options)若包含-XX:+UseG1GC但NM所在节点JDK版本过低(< JDK8u20),或设置了-Xmx超过NM单容器最大内存限制(yarn.nodemanager.resource.memory-mb),JVM会直接崩溃退出,NM日志中仅有一行ExitCodeException exitCode=1,无堆栈。Kerberos票据传递失效:在启用了Kerberos的安全集群中,Spark客户端需将TGT(Ticket Granting Ticket)加密后随应用提交请求一并发送给RM。RM再将其注入AM容器环境。若客户端
kinit过期、krb5.conf配置错误、或yarn.resourcemanager.principal与Keytab不匹配,AM启动后因无法访问HDFS/YARN,会在几秒内自杀,但RM可能来不及更新状态,仍显示ACCEPTED。
这些环节,任何一个出问题,都不会在Spark Driver日志里体现,因为Driver根本没机会启动;也不会在YARN RM Web UI的Application详情页里报错,因为AM从未注册。你唯一能看到的,就是那个沉默的ACCEPTED。
2.3 为什么ACCEPTED比RUNNING更难诊断?
RUNNING状态下的问题,通常伴随明确的日志线索:Executor OOM、Shuffle Fetch Failed、Task not serializable。而ACCEPTED状态的诊断难点在于信息源的天然割裂:
- Spark客户端日志:只记录“已提交Application ID application_1678901234567_0001”,之后静默;
- YARN RM日志(
$HADOOP_LOG_DIR/yarn-*-resourcemanager-*.log):仅记录“Application application_1678901234567_0001 transitioned from SUBMITTED to ACCEPTED”,无后续; - YARN NM日志(
$HADOOP_LOG_DIR/yarn-*-nodemanager-*.log):可能包含Container launch failed、Failed to localize resources等关键错误,但需登录每台NM节点手动grep,且日志量巨大; - HDFS审计日志(
$HADOOP_LOG_DIR/hadoop-*-namenode-*.log):若问题出在JAR下载,此处会有OPEN、GETFILEINFO失败记录,但默认关闭,需提前开启dfs.namenode.audit.loggers。
这种信息分散性,迫使排查者必须建立一套跨组件、跨节点、跨日志类型的关联分析框架,而非依赖单一日志源。这也是本文后续章节将提供结构化排查清单的根本原因。
3. 实操排查路径:从现象到根因的四层穿透法
3.1 第一层:快速验证——确认ACCEPTED是否真为阻塞,而非短暂延迟
在深入日志前,先做三件极简但关键的事,排除90%的误判:
检查YARN队列资源水位
执行yarn queue -status <queue-name>(如default),重点关注:capacity:队列配置容量(百分比)usedCapacity:当前已用容量(若≥100%,说明队列已满)maxCapacity:队列最大弹性容量(若usedCapacity接近maxCapacity,需检查是否有其他大作业占满资源)
注意:
usedCapacity显示为0,并不代表无资源竞争。某些YARN调度器(如CapacityScheduler)支持“预留容器”(Reservation),这些容器不计入usedCapacity,但会实际占用NM资源。此时需查看yarn.scheduler.capacity.<queue>.maximum-capacity是否被其他应用预留。确认ApplicationMaster所需资源是否可满足
Spark提交时指定的AM资源,由以下参数决定:--driver-memory(Client模式)或--am-memory(Cluster模式)--driver-cores或--am-cores
这些值必须 ≤ YARN单节点最大可分配资源(
yarn.nodemanager.resource.memory-mb和yarn.nodemanager.resource.cpu-vcores)。例如,若NM配置为memory-mb=16384(16GB),而你提交--am-memory 18g,则AM容器永远无法被调度,状态将永久ACCEPTED。验证方法:# 在任意NM节点执行,查看实际配置 grep "yarn.nodemanager.resource" $HADOOP_CONF_DIR/yarn-site.xml检查HDFS临时目录可用性
Spark默认将应用JAR上传至/user/<username>/.sparkStaging。执行:hdfs dfs -du -h /user/<username>/.sparkStaging hdfs dfs -ls -d /user/<username>/.sparkStaging若该目录不存在,Spark会尝试创建,但若父目录
/user/<username>权限为drwx------(仅属主可写),而HDFS超级用户(如hdfs)未被授予/user写权限,则创建失败,AM因找不到JAR而无法启动。此时hdfs dfs -mkdir会报Permission denied,但Spark客户端日志不显示此错误。
这三层验证,5分钟内可完成。若全部通过,则问题必然在更深层,进入第二层排查。
3.2 第二层:日志锚定——精准定位故障组件与错误关键词
ACCEPTED状态的根因,95%以上集中在NM侧。因此,日志排查必须以NM为核心,采用“由果溯因”策略:
第一步:从YARN Web UI定位目标NM节点
访问http://<rm-host>:8088/cluster/app/<application_id>,在Application详情页找到:
Tracking URL:若为空,说明AM未注册,问题在AM启动环节;Node列:显示AM应运行的NM主机名(如node03.example.com);Logs链接:点击后跳转至NodeManager的Log Aggregation页面(需启用yarn.log-aggregation-enable=true)。
第二步:在目标NM节点抓取关键日志
登录node03.example.com,执行:
# 查找最近一次AM容器的日志(Container ID通常为container_e*) cd $HADOOP_LOG_DIR grep -r "application_1678901234567_0001" . | head -5 # 输出类似:./yarn-yarn-nodemanager-node03.example.com.log:2023-03-15 10:23:45,123 INFO ... Container container_e03_1678901234567_0001_01_000001 launched # 定位该Container的stdout/stderr日志 ls -lt $HADOOP_LOG_DIR/userlogs/application_1678901234567_0001/container_e03_1678901234567_0001_01_000001/ # 查看stderr(AM启动失败的直接证据) cat $HADOOP_LOG_DIR/userlogs/application_1678901234567_0001/container_e03_1678901234567_0001_01_000001/stderr第三步:识别四大高频错误模式
根据多年实战,ACCEPTED状态下NM stderr日志中,以下关键词出现频率最高,对应不同根因:
| 错误关键词 | 根因定位 | 典型日志片段 |
|---|---|---|
Failed to localize resources | HDFS JAR下载失败 | org.apache.hadoop.yarn.exceptions.YarnException: Failed to localize resources for container_e03_... |
DiskErrorException | NM本地磁盘不可写 | org.apache.hadoop.util.DiskChecker$DiskErrorException: Could not find any valid local directory for ... |
ExitCodeException exitCode=1 | JVM启动失败 | Container exited with a non-zero exit code 1. Last 4096 bytes of stderr : Error: Could not create the Java Virtual Machine. |
Kerberos login failure | 安全认证失败 | javax.security.auth.login.LoginException: Unable to obtain password from user |
注意:
ExitCodeException是最具迷惑性的。它后面往往跟着JVM的原始错误,但Spark的日志框架会截断长输出。务必用tail -n 100查看完整stderr,而非只看最后一行。
3.3 第三层:环境深挖——验证NM节点底层运行时健康度
当日志指向具体错误(如DiskErrorException),需立即验证NM节点的底层环境。这不是“重启服务”式的粗暴操作,而是基于Linux系统原理的精准诊断:
案例:DiskErrorException的深度排查
该错误表明YARN Localizer无法在yarn.nodemanager.local-dirs指定的路径写入文件。常见原因有三:
磁盘空间耗尽:
df -h显示/var/log/hadoop-yarn分区使用率100%,但du -sh /var/log/hadoop-yarn/*总和远小于100%。这是因为Linux的reserved blocks(默认5%)被root用户独占,普通用户(如yarn)无法使用。解决方案:# 查看reserved blocks比例 dumpe2fs -h /dev/sdb1 | grep "Reserved block count" # 临时降低至1%(生产环境谨慎) sudo tune2fs -r 10240 /dev/sdb1inodes耗尽:
df -i显示/var/log/hadoop-yarn的IUse%为100%,但df -h显示空间充足。这是因为YARN Localizer为每个JAR创建大量小文件(.crc校验、tmp临时文件),耗尽inode。解决方案:# 清理过期localization缓存(YARN 3.2+支持) yarn --daemon stop nodemanager rm -rf /var/log/hadoop-yarn/nm-local-dir/filecache/* yarn --daemon start nodemanagerSELinux或AppArmor阻止写入:
ls -Z /var/log/hadoop-yarn/nm-local-dir显示context为system_u:object_r:var_log_t:s0,但YARN进程需要system_u:object_r:hadoop_namenode_exec_t:s0。解决方案:# 临时禁用SELinux验证(不推荐生产) sudo setenforce 0 # 永久方案:修改context sudo semanage fcontext -a -t hadoop_namenode_exec_t "/var/log/hadoop-yarn/nm-local-dir(/.*)?" sudo restorecon -Rv /var/log/hadoop-yarn/nm-local-dir
案例:ExitCodeException exitCode=1的JVM诊断
这不是Java代码异常,而是操作系统级进程启动失败。需用strace追踪:
# 在NM节点,模拟AM启动命令(从yarn-site.xml中提取) sudo -u yarn strace -f -e trace=open,execve,write -o /tmp/am-strace.log \ java -Xmx2g -Djava.io.tmpdir=/var/log/hadoop-yarn/nm-local-dir/nmPrivate \ -cp "/opt/hadoop/share/hadoop/yarn/hadoop-yarn-server-web-proxy-3.3.4.jar:..." \ org.apache.spark.deploy.yarn.ApplicationMaster \ --class 'org.apache.spark.deploy.SparkSubmit' \ --jar 'hdfs://nameservice1/user/spark/.sparkStaging/application_1678901234567_0001/__spark_libs__123456789.jar'/tmp/am-strace.log中若出现open("/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/libjvm.so", O_RDONLY) = -1 ENOENT,说明JDK路径错误;若出现execve("/bin/java", ["java", "-Xmx2g", ...], [/* 42 vars */]) = -1 ENOEXEC,说明/bin/java是shell脚本而非二进制,需检查JAVA_HOME。
3.4 第四层:配置审计——逐项验证Spark与YARN关键参数一致性
ACCEPTED问题的终极根因,往往是Spark客户端、YARN RM、YARN NM三方配置的微小不一致。需建立一张交叉验证表:
| 配置项 | Spark客户端来源 | YARN RM来源 | YARN NM来源 | 不一致后果 | 验证命令 |
|---|---|---|---|---|---|
yarn.resourcemanager.address | --master yarn://<rm-host>:8032 | yarn-site.xml | yarn-site.xml | AM无法连接RM | telnet <rm-host> 8032 |
yarn.nodemanager.local-dirs | 无(由NM提供) | 无 | yarn-site.xml | Localizer无处写入 | ls -ld $(grep "yarn.nodemanager.local-dirs" $HADOOP_CONF_DIR/yarn-site.xml | cut -d'>' -f2 | cut -d'<' -f1) |
spark.yarn.stagingDir | --conf spark.yarn.stagingDir=hdfs://... | spark-defaults.conf | 无 | JAR上传路径错误 | hdfs dfs -ls $(grep "spark.yarn.stagingDir" $SPARK_HOME/conf/spark-defaults.conf | cut -d' ' -f2) |
yarn.scheduler.maximum-allocation-mb | 无 | capacity-scheduler.xml | 无 | AM申请内存超限被拒绝 | yarn scheduler -list |
实操技巧:一键生成配置一致性报告
编写一个Bash脚本check-yarn-consistency.sh:
#!/bin/bash APP_ID="application_1678901234567_0001" echo "=== Spark Client Config ===" spark-submit --master yarn --deploy-mode cluster --conf spark.yarn.stagingDir=hdfs://nameservice1/user/spark/staging --conf spark.debug.maxToStringFields=100 --conf spark.sql.adaptive.enabled=false --class org.apache.spark.examples.SparkPi $SPARK_HOME/examples/jars/spark-examples_2.12-3.3.4.jar 10 2>&1 | grep -E "(stagingDir|yarn\.address)" echo -e "\n=== YARN RM Config ===" grep -E "(yarn\.resourcemanager\.address|yarn\.scheduler\.maximum)" $HADOOP_CONF_DIR/yarn-site.xml echo -e "\n=== YARN NM Config (on node03) ===" ssh node03.example.com "grep -E '(yarn\.nodemanager\.local-dirs|yarn\.nodemanager\.resource)' $HADOOP_CONF_DIR/yarn-site.xml"运行后,人工比对三处输出,任何一项值不一致,即为ACCEPTED的潜在元凶。
4. 核心修复与规避策略:从临时解法到架构级预防
4.1 立竿见影的五种临时修复方案
当生产环境告警响起,首要目标是恢复业务,而非根治。以下方案经千次线上验证,平均修复时间<3分钟:
强制清理NM本地缓存
# 在所有NM节点执行(避免逐台登录,用pssh) pssh -h nm-hosts.txt -l yarn -i "rm -rf /var/log/hadoop-yarn/nm-local-dir/filecache/* && systemctl restart hadoop-yarn-nodemanager"原理:
filecache目录存储已下载的JAR,若其中某个JAR损坏(如网络中断导致下载不全),Localizer会反复尝试校验失败。清空后,AM会重新下载完整JAR。动态调整队列资源上限
# 临时提升default队列最大容量(需RM重启或热加载) echo "yarn.scheduler.capacity.root.default.maximum-capacity=120" >> $HADOOP_CONF_DIR/capacity-scheduler.xml yarn rmadmin -refreshQueues适用场景:
usedCapacity已达100%,但maxCapacity为100%,无弹性空间。此操作将队列上限提至120%,允许新作业抢占资源。绕过HDFS staging,改用本地JAR
spark-submit \ --master yarn \ --deploy-mode cluster \ --conf spark.yarn.jars="file:///opt/spark/jars/*" \ # 直接指向NM本地jar目录 --class org.apache.spark.examples.SparkPi \ /opt/spark/examples/jars/spark-examples_2.12-3.3.4.jar 10优势:完全规避HDFS读取失败风险,适用于JAR包不大(<100MB)且集群NM节点JAR版本统一的场景。
降级AM内存申请
spark-submit \ --master yarn \ --deploy-mode cluster \ --driver-memory 1g \ # 从2g降至1g --driver-cores 1 \ --class org.apache.spark.examples.SparkPi \ $SPARK_HOME/examples/jars/spark-examples_2.12-3.3.4.jar 10依据:
yarn.nodemanager.resource.memory-mb常设为16384(16GB),但单容器最大内存默认为8192(8GB)。若AM申请2g,虽小于8g,但若NM内存碎片化严重,可能无法分配连续2g块。降至1g成功率大幅提升。禁用Log Aggregation(针对日志丢失)
# 在spark-submit中添加 --conf spark.yarn.logAggregationEnabled=false效果:AM日志将直接输出到NM本地
$HADOOP_LOG_DIR/userlogs/.../stdout,避免因Log Aggregation服务故障导致日志不可见,便于快速定位ExitCodeException。
注意:以上均为临时措施,切勿长期使用。它们解决了“能不能跑”,但未解决“为什么不能稳定跑”。
4.2 架构级预防:构建ACCEPTED免疫的Spark-YARN生产环境
真正的稳定性,源于设计之初的防御性架构。以下是某金融公司数据平台沉淀的四大黄金实践:
实践一:NM本地磁盘的“双保险”配置
不依赖单一local-dirs,而是配置多路径并启用自动故障转移:
<!-- yarn-site.xml --> <property> <name>yarn.nodemanager.local-dirs</name> <value>/data1/yarn/local,/data2/yarn/local,/var/log/hadoop-yarn/nm-local-dir</value> </property> <property> <name>yarn.nodemanager.disk-health-checker.min-healthy-disks</name> <value>0.5</value> <!-- 至少50%路径健康才上报healthy --> </property>效果:当/data1磁盘满时,Localizer自动切换至/data2,AM启动不受影响。min-healthy-disks=0.5确保即使一块盘故障,NM仍参与调度。
实践二:Spark Staging目录的HDFS Quota硬隔离
为每个业务线创建独立staging目录,并设置硬配额:
# 创建目录 hdfs dfs -mkdir -p /user/dataeng/.sparkStaging hdfs dfs -chown dataeng:hadoop /user/dataeng/.sparkStaging # 设置10GB硬配额(超出则写入失败,而非静默阻塞) hdfs dfsadmin -setSpaceQuota 10737418240 /user/dataeng/.sparkStaging价值:避免单个用户上传海量小JAR耗尽HDFS空间,导致所有Spark作业ACCEPTED。配额超限时,Spark客户端会立即报QuotaExceededException,而非陷入等待。
实践三:AM启动的“健康门禁”机制
在AM主类中嵌入启动自检逻辑,失败时主动退出并上报明确错误:
// 自定义ApplicationMaster入口(继承org.apache.spark.deploy.yarn.ApplicationMaster) override def run(): Unit = { // 启动前检查 if (!checkHDFSAvailable()) { System.err.println("FATAL: HDFS is unavailable. Exiting to avoid ACCEPTED deadlock.") System.exit(101) // 非0退出码,触发YARN重试 } if (!checkLocalDirsWritable()) { System.err.println("FATAL: NM local dirs are unwritable.") System.exit(102) } super.run() }效果:将原本静默的ACCEPTED,转化为明确的FAILED状态,并在YARN UI中显示ExitCode 101,大幅缩短MTTR(平均修复时间)。
实践四:YARN ResourceManager的“ACCEPTED超时熔断”
修改RM源码,为ACCEPTED状态添加强制超时:
// 修改org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMAppImpl private static final long ACCEPTED_TIMEOUT_MS = 120000L; // 2分钟 @Override protected void handleStateChange() { if (getState() == RMAppState.ACCEPTED) { // 启动超时监控线程 new Timer().schedule(new TimerTask() { @Override public void run() { if (getState() == RMAppState.ACCEPTED) { LOG.warn("Application {} stuck in ACCEPTED for {}ms. Forcing to FAILED.", getApplicationId(), ACCEPTED_TIMEOUT_MS); this.transitionToFinalState(RMAppState.FAILED); } } }, ACCEPTED_TIMEOUT_MS); } }收益:杜绝ACCEPTED无限期悬挂,所有问题在2分钟内暴露为FAILED,驱动运维团队必须介入,形成闭环。
5. 常见问题速查表与独家避坑心得
5.1 高频问题速查表(按症状索引)
| 现象描述 | 可能根因 | 快速验证命令 | 解决方案 |
|---|---|---|---|
yarn application -list显示ACCEPTED,但yarn logs -applicationId <id>报Log aggregation has not completed | Log Aggregation服务未启用或失败 | yarn logs -applicationId <id> -am ALL 2>&1 | head -20 | 在yarn-site.xml中设置yarn.log-aggregation-enable=true,重启RM |
| 提交Spark SQL作业ACCEPTED,但提交Spark Shell正常 | Spark SQL依赖的spark-sql包未上传至staging目录 | hdfs dfs -ls /user/<user>/.sparkStaging/application_*/ | 添加--jars $SPARK_HOME/jars/spark-sql_2.12-3.3.4.jar显式指定 |
| 所有Spark作业ACCEPTED,但MapReduce作业正常 | Spark的spark-yarn模块JAR与Hadoop版本不兼容 | ls $SPARK_HOME/jars/ | grep hadoopvshadoop version | 下载与Hadoop同版本的Spark发行版,或手动替换spark-yarnJAR |
| ACCEPTED状态在集群升级后首次出现 | YARN RM的yarn.resourcemanager.scheduler.class从CapacityScheduler改为FairScheduler,但队列配置未迁移 | yarn scheduler -list | 检查$HADOOP_CONF_DIR/fair-scheduler.xml是否存在,或回退至CapacityScheduler |
kubectl get pods显示Spark Driver Pod处于Pending(K8s on YARN场景) | Kubernetes Node资源不足,无法调度Driver Pod | kubectl describe pod <driver-pod> | 增加Node资源,或调整spark.kubernetes.driver.request.cores |
5.2 我踩过的七个深坑与血泪心得
“HDFS高可用NameService配置陷阱”
当HDFS启用HA,core-site.xml中fs.defaultFS设为hdfs://nameservice1,但Spark客户端未正确解析该nameservice,会尝试连接nameservice1这个不存在的主机。心得:Spark 3.0+需显式配置spark.hadoop.fs.defaultFS=hdfs://nameservice1,否则fallback到core-site.xml的解析可能失败。“NM磁盘IO瓶颈伪装成资源不足”
iostat -x 1显示%util持续100%,但df -h空间充足。此时NM的Localizer因IO等待超时,反复重试下载JAR,导致ACCEPTED。心得:监控iostat的await(平均IO等待时间),>100ms即为瓶颈,需优化磁盘或增加local-dirs路径。“Spark 3.3+的JVM 17兼容性雷区”
Spark 3.3默认支持JDK 17,但YARN NM若运行在JDK 8上,AM启动时--add-opens参数会被JVM 8忽略,导致反射失败。心得:统一集群JDK版本,或在spark-env.sh中设置export SPARK_DAEMON_JAVA_OPTS="-Djdk.lang.Process.launchMechanism=vfork"。“Kerberos TGT有效期与AM生命周期错配”
kinit -l 24h获取的TGT,若AM运行时间超过24小时(如流式作业),TGT过期后AM无法续订,静默失败。心得:在AM启动脚本中加入kinit -R定期续订,或配置krb5.conf的renew_lifetime = 7d。“Docker容器化NM的cgroup v2兼容问题”
在CentOS 8+启用cgroup v2的宿主机上,Docker运行的NM容器无法正确读取/sys/fs/cgroup/memory,导致yarn.nodemanager.resource.memory-mb读取为0。心得:启动容器时添加--cgroup-parent /sys/fs/cgroup/unified,或降级至cgroup v1。“Spark Thrift Server的ACCEPTED雪崩”
多个用户并发连接Thrift Server,每个连接触发一个Spark Session,导致AM实例激增,瞬间耗尽YARN队列资源。心得:Thrift Server必须配置spark.sql.thriftServer.incrementalCollect=true,并限制spark.sql.thriftServer.maxConnections。“云厂商虚拟机的NUMA拓扑误导”
AWS r5.4xlarge实例标称16 vCPU,但实际为2 NUMA节点各8核。若yarn.nodemanager.resource.cpu-vcores=16,而Spark AM申请--am-cores=16,YARN可能跨NUMA分配,导致性能骤降并触发超时。心得:vcores应设为单NUMA节点核心数(如8),并通过numactl --cpunodebind=0绑定AM到单节点。
最后分享一个小技巧:在
spark-submit命令后追加--conf spark.sql.adaptive.enabled=false --conf spark.sql.adaptive.coalescePartitions.enabled=false。这两个自适应查询优化特性在YARN资源紧张时,会引发额外的资源申请竞争,加剧ACCEPTED概率。生产环境建议关闭,待集群资源充裕后再启用。
我在实际使用中发现,ACCEPTED问题的解决,70%靠日志定位,20%靠配置审计,10%靠环境深挖。但真正拉开高手差距的,是那10%——当你看到DiskErrorException时,能否想到是inodes耗尽而非磁盘空间;当你看到ExitCodeException时,能否用strace追踪到libjvm.so缺失。这些能力,没有捷径,唯有在一次次深夜的grep、ssh、strace中淬炼而成。这个状态,不是故障,而是YARN在向你发出邀请:来深入它的内脏,