调度器决定资源分配顺序,应用生命周期决定一个作业怎样用这些资源从提交走到完成。

上一篇讲 FIFO、Capacity、Fair 的取舍,本篇只看一个问题:提交、调度、运行、完成如何映射到 YARN 的公共状态和内部事件。下一篇会继续讲 ResourceManager HA 与 Federation,本篇不会展开 HA、Timeline Service 或 MapReduce 深层机制。

生命周期常被写成“提交 → 运行 → 结束”。这个说法太粗。YARN 里要分成三层状态机看:RM 维护 application,RM 维护 application attempt,NM 维护 container。对外暴露的公共状态很少,对内用于启动、收尾、清理和重试的阶段更细。排障时把三层混在一起,最容易把 AM 重试、container 失败和 application 最终状态误判成同一个问题。

链路固定为 Client → RM → NM → AM → Container。Client 提交应用,RM 持久化 application 并调度 AM,NM 启动 AM container,AM 注册后通过 allocate() 心跳申请后续 container,container 完成后状态再回到 RM 和 AM。

核心问题

一个 YARN 应用不是一个进程,而是一组被 ResourceManager 串起来的状态对象。

1
2
3
Application         作业整体命运,由 RM 维护
ApplicationAttempt 一次 AM 尝试,由 RM 维护
Container 一个执行单元,由 NM 运行,RM/AM 观察

三个问题要分开回答:

问题 看哪一层 典型入口
作业现在是排队、运行、成功、失败还是被杀 Application yarn application -status、RM REST
AM 这次尝试是否已经启动、是否正在收尾、是否失败 ApplicationAttempt yarn applicationattempt -list
某个执行单元是否已分配、运行、完成,退出码是什么 Container yarn container -list、RM REST container 字段

这个分层决定了文章后面的所有判断。application 的公共状态不表达“正在清理 container”这种细节;container 的状态也不能反推整个框架任务一定成功。

三层状态机

YARN 应用的状态要分层看。

层级 所属组件 公开状态
Application RM / REST / Client API NEW / NEW_SAVING / SUBMITTED / ACCEPTED / RUNNING / FINISHED / FAILED / KILLED
ApplicationAttempt RM / attempt report NEW / SUBMITTED / SCHEDULED / ALLOCATED_SAVING / ALLOCATED / LAUNCHED / FAILED / RUNNING / FINISHING / FINISHED / KILLED
Container API / REST container state NEW / RUNNING / COMPLETE

YarnApplicationState 只有 8 个值。应用级公开状态没有“收尾中”的中间态;FINISHING 是 application attempt 层的状态,不是 application 层的状态。

container 也有同样的边界。公开 ContainerState 只有 NEW / RUNNING / COMPLETE。NM 内部会经历 localizing、localized、killing、done 等更细阶段;在 3.4.1 的 API 层,面向外部的细分抽象应标为 ContainerSubState,不是 ContainerState

1
2
3
ContainerState     NEW / RUNNING / COMPLETE
ContainerSubState SCHEDULED / RUNNING / PAUSED / COMPLETING / DONE
NM 内部实现阶段 LOCALIZING / LOCALIZED / KILLING / EXITED_WITH_SUCCESS / ...

这几个名字看起来接近,语义不同。ContainerState.COMPLETE 表示容器生命周期已经完成,不等于任务成功;成功还要看 ContainerStatus 里的 exit status 和 diagnostics。

提交流程

从客户端看,最短路径是:

1
2
3
4
5
6
7
8
9
Client
-> getNewApplication()
-> 上传 jar / 配置 / 本地资源到 HDFS
-> submitApplication(ApplicationSubmissionContext)
-> RM 保存 application 元数据
-> RM 调度 AM container
-> NM 启动 AM container
-> AM registerApplicationMaster()
-> AM allocate() 心跳申请后续 container

getNewApplication() 只是向 RM 申请一个全局唯一的 ApplicationId。真正的生命周期从 submitApplication() 开始。

ApplicationSubmissionContext 里最重要的是 AM 的启动命令、本地资源、队列、优先级、资源需求和安全令牌。RM 收到提交后,会把 application 元数据保存到 state store,然后推进到 SUBMITTEDACCEPTED

AM 被建模为第一个 application attempt。这个 attempt 先进入 SCHEDULED,拿到 AM container 后进入 ALLOCATED,NM 拉起容器后进入 LAUNCHED。AM 调用 registerApplicationMaster() 成功后,attempt 才进入 RUNNING,application 也随之进入 RUNNING

这个过程里,调度器只决定“什么时候给 AM container、后续 container 分到哪里”。它不负责解释框架内部任务图。MapReduce 的 task attempt、Spark 的 stage、Flink 的 checkpoint 都在 YARN application 之上。

register 和 allocate

AM 启动后的第一步是注册,还不会直接拿到所有资源。

registerApplicationMaster() 负责告诉 RM:这个 AM 在哪台机器上、监听哪个 RPC 端口、tracking URL 是什么。ApplicationAttemptReport 里也会出现 host、rpcPort、trackingUrl、AMContainerId 这些字段。

真正的资源协商走 ApplicationMasterProtocol.allocate(AllocateRequest)。这个调用是 AM 到 RM 的同步心跳 RPC。AMRMClientAsync 只是客户端包装:它在线程里周期性调用同步 allocate(),再通过回调把结果交给 AM 代码。协议本身不是异步 RPC。

AllocateRequest 里通常要关注这些字段:

字段 含义
responseId 防止 AM 和 RM 之间的响应乱序、重复处理
progress AM 上报应用进度
ask 申请新 container 的 ResourceRequest 列表
release 主动释放的 ContainerId 列表
blacklist / update 类字段 版本扩展,用于节点黑名单、container 更新等能力

完成容器不在 AllocateRequest 里。完成信息由 RM 在 AllocateResponse.getCompletedContainersStatuses() 里返回,类型是 List<ContainerStatus>

AllocateResponse 要分两类读:

返回项 类型 排障意义
新分配的 container List<Container> AM 接下来可以让 NM 启动的新执行单元
已完成的 container 状态 List<ContainerStatus> container 退出状态、diagnostics、完成原因
headroom / available resources Resource 当前队列和调度策略下还能申请多少资源
updated nodes List<NodeReport> 节点状态变化
previous attempt containers List<Container> RM 重启或 AM attempt 场景下可恢复观察的历史 container

Container 描述“分配到哪里、资源是多少”;ContainerStatus 描述“最后怎么退出”。把两者写反,会直接误导排障路径。

Container 启动与回收

AM 拿到新 container 后,会通过 ContainerManagementProtocol.startContainers() 请求 NM 启动进程。

ContainerLaunchContext 里放的是命令、本地资源、环境变量和 token。NM 收到请求后,先做本地资源 localization,再启动用户进程。用户进程退出后,NM 收集退出码、诊断信息和日志,再把完成状态通过心跳回传。

公开层面只需要记住三态:

1
NEW -> RUNNING -> COMPLETE

内部实现会更细:

1
localization -> launch -> running -> exit -> cleanup -> done

如果容器被杀,内部路径还会经过 killing 和资源清理。对外查询时,不要期待 REST 的 containerState 暴露这些内部阶段;RM REST container 字段里的 containerState 仍然是公开 container 状态。

COMPLETE 也不是“任务成功”。判断一个 container 是否成功,至少要同时看:

字段 判据
container state 是否进入 COMPLETE
exit status 是否为成功退出码
diagnostics 是否有框架、NM、资源、权限或超时诊断
log URL 是否能继续定位 stdout、stderr、syslog

实验:观察一次应用生命周期

实验状态:UNVERIFIED_RUNTIME。下面是验证步骤,本轮没有连接真实 YARN 集群执行。

下面的实验只给命令和观察判据,不声称已经在某个集群跑出固定结果。不同队列、调度器、权限和日志聚合配置会影响输出字段。

准备一个真实存在的 application id:

1
yarn application -list

观察 application 层:

1
yarn application -status <applicationId>

重点看这些字段:

字段 映射
Application-Id RMApp 的公开 id
Application-StateState YarnApplicationState
Final-State 框架上报给 RM 的最终状态
Tracking-URL AM 或历史服务提供的排障入口
Queue 调度器队列,承接上一篇调度器分析

判据很简单:application state 只能落在 NEW / NEW_SAVING / SUBMITTED / ACCEPTED / RUNNING / FINISHED / FAILED / KILLED 这组值里。出现其他应用级状态时,先检查命令来源、REST 字段层级或文章/脚本是否把 attempt 状态混进来了。

观察 attempt 层:

1
yarn applicationattempt -list <applicationId>

重点看 application attempt id、attempt state、AM host、RPC port 和 tracking URL。一个 application 可以有多个 attempt;AM 崩溃或超时后,后一个 attempt 才是新的编排器。

观察 container 层:

1
yarn container -list <applicationAttemptId>

重点看 container id、node id、container state、exit status、log URL。字段映射如下:

字段 映射
Container-Id 一个执行单元
NodeId container 被分配到的 NM
State 公开 ContainerState
ExitStatus 成败判断的关键字段
LogURL 后续日志定位入口

NM Web UI 默认端口是 8042,所以日志 URL 常见形态是 http://node-host:8042/node/containerlogs/...。但 NM RPC 地址默认是 ${yarn.nodemanager.hostname}:0,实际 NodeId 端口由部署决定,不应把 8042 当成所有 NM 端口。

AM liveness 和重试

AM 需要持续向 RM 心跳。yarn.am.liveness-monitor.expiry-interval-ms 在 Hadoop 3.4.1 的默认值是 900000 毫秒,也就是 15 分钟。超过这个时间没有 AM 汇报,RM 会认为 AM 不再存活。

如果 AM 崩溃,RM 可以尝试拉起新的 attempt。yarn.resourcemanager.am.max-attempts 的默认值是 2,表示默认最多两次 AM attempt,也就是首尝试之外还有一次重试机会。单个 application 也可以在提交时设置自己的最大 attempt 数,但不能超过全局上限。

重试时要把边界写清楚:YARN 负责重新启动 AM container,不负责保证框架内部状态恢复成功。新 AM 要怎样重建任务图、怎样处理已经完成或正在运行的工作,由具体框架自己负责。

RM Restart 的官方文档能支持两件事:

能力 YARN 负责什么
非 work-preserving RM restart 从 state store 恢复 application / attempt 元数据,重新拉起先前运行的 application
work-preserving RM restart RM 重启后结合 NM container 状态和 AM container requests 重建 RM 运行态,尽量不杀掉原有工作

这仍然不是“任何框架任务必然恢复”。YARN 给框架一个新的或可继续通信的 AM 入口,框架恢复语义要看框架自己的 checkpoint、history、lineage 或作业协议。

失败、kill 与日志保留

正常完成时,AM 调用 finishApplicationMaster(FINISHED, diagnostics, trackingUrl),RM 收到后推进 attempt 和 application 的最终状态。失败或被杀同理,最终状态由 AM 上报、RM 处理或用户操作共同决定。

用户主动终止应用的官方 CLI 是:

1
yarn application -kill <applicationId>

RM REST 也提供 application state 更新接口,可把 application state 更新为 KILLED。这里要谨慎表述:kill 是终止请求,不应写成“命令返回时所有相关进程已经同步消失”。RM、AM、NM、container 清理之间仍然通过心跳、事件和清理流程推进。

日志保留也不是一刀切。没有开启 log aggregation 时,yarn.nodemanager.log.retain-seconds 默认是 10800 秒,也就是 3 小时。开启 log aggregation 后,长期排障路径转向聚合日志,NM 本地日志不再是唯一入口。

工程映射与排障表

生命周期排障最怕跨层下结论。下面这张表可以直接用于迁移旧脚本、改监控规则或排查线上工单。

现象 先看字段 常见原因 下一步
应用长期 ACCEPTED application state、queue、headroom 队列资源不足、AM resource 受限、队列 ACL 或 capacity 限制 回到调度器队列看 used/capacity/maxCapacity 和 AM 资源上限
应用 RUNNING 但没有业务进度 allocate() progress、AM log、container 列表 AM 活着但没有拿到新 container,或框架内部任务阻塞 AllocateResponse 的 allocated containers、completed statuses 和 diagnostics
container COMPLETE 但作业失败 exit status、diagnostics、AM 判断 容器生命周期结束,不代表框架任务成功 ContainerStatus 和框架日志判断失败类型
多个 attempt applicationattempt list、AM diagnostics AM 崩溃、超时、被杀或 RM 重启恢复 对照 yarn.am.liveness-monitor.expiry-interval-ms 和 max attempts
kill 后仍能看到日志或部分状态 app state、container cleanup、log aggregation 终止请求与清理、日志保留不是同步完成 看 NM 日志保留配置和聚合日志入口

迁移旧监控时,最小改法是先按三层拆指标:application 用 YarnApplicationState,attempt 用 YarnApplicationAttemptState,container 用 ContainerState + exitStatus + diagnostics。需要内部阶段时,再显式标成 ContainerSubState 或 NM implementation phase。

常见误解

误解 1:应用公开状态里有收尾中状态

没有。YarnApplicationState 只有 8 个值。FINISHING 属于 application attempt 层,不能写成 application 层状态。看到“应用正在 finishing”这类说法时,要先问它指的是 attempt report、RM 内部事件,还是作者把层级混了。

误解 2:Application FINISHED 等于每个框架任务成功

不等于。YARN 的 application state 是框架与 RM 协作后的最终状态,container 的 COMPLETE 只是生命周期结束。框架任务是否成功,要结合框架自己的 final status、container exit status、diagnostics 和历史服务。

误解 3:AM 可重试等于框架恢复一定成功

不等于。YARN 只负责重新启动 AM container,或者在 RM restart 场景下重建 RM 侧状态并让 NM、AM resync。框架内部是否能恢复任务图、状态和未完成工作,由框架自己负责。

模式提炼

YARN 应用生命周期可以概括成两个可迁移模式。

模式 公式 用在什么地方
分层状态机 整体状态 != 尝试状态 != 执行单元状态 application / attempt / container 排障
同步协议异步包装 同步 RPC + 心跳线程 + 回调 ApplicationMasterProtocol.allocate()AMRMClientAsync

第一种模式适合所有“上层对象由多次尝试组成,每次尝试又调度多个执行单元”的系统。看到作业、attempt、task、container、executor 这些词并存时,先画层级,不要直接拿底层失败解释顶层状态。

第二种模式适合理解很多客户端 SDK。协议层可能是同步 RPC,客户端库为了易用加上后台线程、future 或 callback。排障时要回到协议边界看请求和响应:请求带了什么,响应返回了什么,哪一层负责重试。

练习

  1. 找一个已经完成的 YARN application,用 yarn application -statusyarn applicationattempt -listyarn container -list 分别记录 application、attempt、container 三层状态,不要把字段合并成一张“作业状态”。

  2. 找一个失败 container,对照 ContainerState、exit status、diagnostics 和 log URL,判断失败发生在 NM 启动、用户进程退出、资源限制还是框架主动失败。

  3. 阅读一个使用 AMRMClientAsync 的示例,标出它最终调用 allocate() 的位置,再说明哪些逻辑属于客户端异步包装,哪些逻辑属于 YARN 协议。

小结

YARN application 的生命周期不是一条线,而是三层状态机叠在一起:RMApp 给出作业整体命运,RMAppAttempt 表示一次 AM 编排尝试,Container 表示一个执行单元。allocate() 把 AM 和 RM 连成持续心跳,AllocateResponse 同时返回新分配的 container 和已完成 container 的状态。

上一篇的调度器决定资源怎么分;本篇的生命周期说明资源分到以后状态怎么走。下一篇进入 ResourceManager HA 与 Federation,重点会从单个 application 的推进,转向 RM 自身如何在重启、切换和多集群路由下继续维持这些状态。

系列导航

序号 主题 状态
00 导读:节点总会失败
01 HDFS 架构与三层切分
02 文件写入路径与流水线
03 文件读取路径与副本选择
04 NameNode 内存模型与启动恢复
05 HDFS HA 与脑裂防御
06 HDFS 3.x 演进与纠删码
07 YARN 架构与三方契约
08 YARN 资源模型、Container 与 NodeLabel
09 YARN 调度器对比:FIFO、Capacity、Fair 上一篇
10 YARN 应用程序生命周期 本篇
11 YARN HA 与 Federation 下一篇
12 YARN Timeline Service v2
13 MapReduce 编程模型与分而治之
14 MapReduce Shuffle 全流程
15 MRv2 on YARN ApplicationMaster 与 Task Attempt
16 Hadoop RPC 协议栈
17 序列化与压缩
18 Hadoop 安全 Kerberos Token ProxyUser
19 监控与运维 Metrics JMX 日志聚合
20 Hadoop 生态 Hive HBase Pig
21 Hadoop 与对象存储 Kubernetes 演进对比
22 Hadoop 设计遗产从 GFS MapReduce 到云原生

参考资料