深入 Hadoop 10 - YARN 应用程序生命周期
调度器决定资源分配顺序,应用生命周期决定一个作业怎样用这些资源从提交走到完成。
上一篇讲 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 | |
三个问题要分开回答:
| 问题 | 看哪一层 | 典型入口 |
|---|---|---|
| 作业现在是排队、运行、成功、失败还是被杀 | 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 | |
这几个名字看起来接近,语义不同。ContainerState.COMPLETE 表示容器生命周期已经完成,不等于任务成功;成功还要看 ContainerStatus 里的 exit status 和 diagnostics。
提交流程
从客户端看,最短路径是:
1 | |
getNewApplication() 只是向 RM 申请一个全局唯一的 ApplicationId。真正的生命周期从 submitApplication() 开始。
ApplicationSubmissionContext 里最重要的是 AM 的启动命令、本地资源、队列、优先级、资源需求和安全令牌。RM 收到提交后,会把 application 元数据保存到 state store,然后推进到 SUBMITTED 和 ACCEPTED。
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 | |
内部实现会更细:
1 | |
如果容器被杀,内部路径还会经过 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 | |
观察 application 层:
1 | |
重点看这些字段:
| 字段 | 映射 |
|---|---|
Application-Id |
RMApp 的公开 id |
Application-State 或 State |
YarnApplicationState |
Final-State |
框架上报给 RM 的最终状态 |
Tracking-URL |
AM 或历史服务提供的排障入口 |
Queue |
调度器队列,承接上一篇调度器分析 |
判据很简单:application state 只能落在 NEW / NEW_SAVING / SUBMITTED / ACCEPTED / RUNNING / FINISHED / FAILED / KILLED 这组值里。出现其他应用级状态时,先检查命令来源、REST 字段层级或文章/脚本是否把 attempt 状态混进来了。
观察 attempt 层:
1 | |
重点看 application attempt id、attempt state、AM host、RPC port 和 tracking URL。一个 application 可以有多个 attempt;AM 崩溃或超时后,后一个 attempt 才是新的编排器。
观察 container 层:
1 | |
重点看 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 | |
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。排障时要回到协议边界看请求和响应:请求带了什么,响应返回了什么,哪一层负责重试。
练习
-
找一个已经完成的 YARN application,用
yarn application -status、yarn applicationattempt -list、yarn container -list分别记录 application、attempt、container 三层状态,不要把字段合并成一张“作业状态”。 -
找一个失败 container,对照
ContainerState、exit status、diagnostics 和 log URL,判断失败发生在 NM 启动、用户进程退出、资源限制还是框架主动失败。 -
阅读一个使用
AMRMClientAsync的示例,标出它最终调用allocate()的位置,再说明哪些逻辑属于客户端异步包装,哪些逻辑属于 YARN 协议。
小结
YARN application 的生命周期不是一条线,而是三层状态机叠在一起:RMApp 给出作业整体命运,RMAppAttempt 表示一次 AM 编排尝试,Container 表示一个执行单元。allocate() 把 AM 和 RM 连成持续心跳,AllocateResponse 同时返回新分配的 container 和已完成 container 的状态。
上一篇的调度器决定资源怎么分;本篇的生命周期说明资源分到以后状态怎么走。下一篇进入 ResourceManager HA 与 Federation,重点会从单个 application 的推进,转向 RM 自身如何在重启、切换和多集群路由下继续维持这些状态。
系列导航
参考资料
- YarnApplicationState 3.4.1 源码
- YarnApplicationAttemptState 3.4.1 源码
- ContainerState 3.4.1 源码
- ContainerSubState 3.4.1 源码
- ApplicationMasterProtocol 3.4.1 源码
- AllocateRequest 3.4.1 源码
- AllocateResponse 3.4.1 源码
- ContainerStatus 3.4.1 源码
- yarn-default.xml 3.4.1
- YARN Commands
- ResourceManager REST APIs
- ResourceManager Restart
