YARN Client 把应用提交给 ResourceManager 以后,真正决定应用如何运行的是 ApplicationMaster。Client 提交的是 ApplicationSubmissionContext,其中包含 AM Container 的启动上下文;ApplicationMaster 启动后,才开始承担应用内部调度、普通 Container 申请、启动协调、状态监控和完成汇报。
如果把 ResourceManager 看成 cluster-level scheduler 和 allocator,那么 ApplicationMaster 就是单个应用自己的协调器。它不负责全局资源调度,也不是普通任务进程。它站在应用内部,知道自己需要多少个 Container、每个 Container 要运行什么命令、失败后是否重试、什么时候算成功、什么时候应该向 RM 汇报失败。
这次我提交了一个 Distributed-Shell 应用,让它在 root.etl 队列里启动 2 个普通 Container:
hadoop jar /opt/hadoop/share/hadoop/yarn/hadoop-yarn-applications-distributedshell-3.4.1.jar \
-jar /opt/hadoop/share/hadoop/yarn/hadoop-yarn-applications-distributedshell-3.4.1.jar \
-appname YarnAMContainerDemoOK \
-queue etl \
-master_memory 512 \
-container_memory 256 \
-num_containers 2 \
-shell_command 'echo YARN_AM_CONTAINER_20250226; hostname; pwd; ls -la; printenv | sort | head -20; sleep 8'
运行结果如下:
Application ID: application_1740547572690_0001
Application Name: YarnAMContainerDemoOK
Application Type: YARN
Queue: root.etl
State: FINISHED
Final-State: SUCCEEDED
Log Aggregation Status: SUCCEEDED

1. AM 与 Container 的启动模型
1.1 ApplicationMaster 先向 ResourceManager 注册
ApplicationMaster 是由 NodeManager 启动的,但它启动以后必须先向 ResourceManager 注册。注册成功以后,RM 才知道这个应用的协调者已经可用,后续资源申请、进度上报和完成汇报也才有主体。
这个注册过程解决了一个边界问题:ResourceManager 启动了 AM Container,但 AM 进程是否真正起来、是否能和 RM 通信,需要 AM 自己确认。RM 页面里的应用从 ACCEPTED 进入 RUNNING,通常就意味着 AM 已经启动并注册。
如果应用长时间停在 ACCEPTED,大概率是 AM Container 没有成功分配或没有成功启动。如果应用进入 RUNNING 后很快失败,就要看 AM 日志或后续普通 Container 的状态。
AM 注册以后,会通过 heartbeat 和 RM 持续通信:向 RM 申请资源,接收分配结果,联系对应 NodeManager 启动 Container,监控 Container 完成情况,最终向 RM 注销并报告结果。如果 AM 进程失败,RM 可以根据应用配置启动新的 application attempt;新 attempt 是否能恢复状态,取决于 AM 自己的状态持久化和幂等设计。
1.2 AM 申请的是普通 Container
Client 提交阶段申请的是 AM Container;AM 启动后申请的是普通 Container。这个区别非常重要。
在这次命令里:
master_memory=512
container_memory=256
num_containers=2
master_memory 决定 AM Container 的资源;container_memory 和 num_containers 决定 AM 后续申请的普通 Container 资源。ResourceManager 页面里能看到应用最终消耗了资源秒数,但真正的容器数量要从日志和 attempt 关系里看。
日志聚合输出里能看到这次应用至少涉及三个 Container:
container_1740547572690_0001_01_000001 ApplicationMaster
container_1740547572690_0001_01_000002 shell command
container_1740547572690_0001_01_000003 shell command
第一个 Container 是 AM,后两个 Container 执行用户指定的 shell 命令。这正是 YARN 应用的典型结构:一个 AM 管理多个普通 Container。
1.3 ResourceRequest 表达资源意图
ApplicationMaster 向 ResourceManager 申请资源时,并不是说“给我某台机器上的一个进程”。它提交的是资源请求。这个请求通常包含:
| 内容 | 含义 |
|---|---|
| memory | Container 内存 |
| vcores | Container vCore |
| priority | 请求优先级,调度器会用它参与同一应用内的请求排序 |
| resourceName | 节点、机架或 *,分别对应 node-local、rack-local 和 ANY |
| capability | 资源能力描述 |
| numContainers | 请求数量 |
MapReduce AM 会根据输入分片和数据本地性申请 Map Task Container,通常先争取 node-local,再退到 rack-local,最后才是 ANY。Distributed-Shell AM 则更简单,只按用户指定数量申请 Container。无论哪种应用,RM 都只负责根据调度器规则返回分配结果,不负责启动和执行用户命令。
这意味着 AM 不能假设请求一定立即满足。队列资源不足、节点资源不足、用户限制、AM 资源比例、节点标签都可能影响分配。一个设计良好的 AM 要能处理等待、部分分配、失败和重试。
1.4 分配结果回来以后,AM 要联系 NodeManager
ResourceManager 给 AM 的是 Container 分配结果。真正启动进程的是 NodeManager。AM 收到分配后,需要拿到 Container 所在节点的信息,并通过 NodeManager 的 ContainerManagementProtocol 发起启动请求。这里的链路可以理解为:AM 向 RM 申请资源,RM 返回分配结果,AM 再联系被分配到的 NM 启动 Container。
这一步经常被误解。ResourceManager 不会替 AM 执行业务命令,它只告诉 AM:“这个 Container 分给你了,位置在某个 NodeManager 上。”AM 再把启动上下文发给该 NodeManager,由 NodeManager 下载资源、解压归档、创建工作目录、生成启动脚本、设置环境变量并启动进程。
这种设计把全局资源分配和节点进程执行分开。RM 保持轻量,不需要知道每个应用的命令细节;NM 专注本机资源和进程;AM 专注应用语义。
1.5 ContainerLaunchContext 决定进程如何启动
AM 启动普通 Container 时,核心对象是 ContainerLaunchContext。它包含启动一个 Container 需要的几乎全部信息:
| 内容 | 说明 |
|---|---|
| LocalResource | 需要本地化的文件、jar、脚本、归档 |
| Environment | 环境变量 |
| Commands | 最终执行的命令 |
| ServiceData | 给辅助服务使用的数据 |
| Tokens | 访问 HDFS、RM、NM 等服务的凭据 |
| ACLs | Container 访问控制 |
换成结构关系可以这样看:
ContainerLaunchContext
├── LocalResource # jar、脚本、配置、归档
├── Environment # 环境变量
├── Commands # 启动命令
├── Tokens # 安全凭据
├── ServiceData # auxiliary service 数据
└── ACLs # 访问控制
Distributed-Shell 的普通 Container 最终执行的是类似这样的命令:
exec /bin/bash -c "echo YARN_AM_CONTAINER_20250226; hostname; pwd; ls -la; printenv | sort | head -20; sleep 8"
这个命令不是用户直接登录节点执行的,而是 AM 通过 ContainerLaunchContext 交给 NodeManager。launch_container.sh 由 NodeManager 根据 CLC 生成和执行,不是 AM 自己在节点上执行脚本。
如果命令里包含复杂引用、括号、重定向或环境依赖,很容易在生成脚本后暴露问题。写 YARN 应用时,应该尽量让启动命令可解释、可记录、可复现,同时注意 shell 注入和引号转义问题。否则失败时只会看到 Container 退出码,很难判断是资源问题、环境问题还是命令本身的问题。
1.6 LocalResource 让 Container 拿到需要的文件
普通 Container 往往不只需要一条命令,还需要 jar、脚本、配置文件、模型文件或压缩包。AM 要把这些资源写入 LocalResource,NodeManager 才能在启动前本地化。
LocalResource 的价值在于统一资源分发。应用不需要自己 scp 到每台机器,也不需要假设每台机器都有相同目录。只要资源在 HDFS 上可访问,NodeManager 就能按类型和可见性把它本地化到 Container 工作目录。NodeManager 还会维护 localization cache,尽量复用已经下载过的资源;timestamp、大小和路径会参与资源版本判断,避免旧资源被误用。
但这也引入了几个排查点:
| 问题 | 可能原因 |
|---|---|
| Container 启动前失败 | LocalResource 路径不存在、HDFS 权限不足或 token 不可用 |
| 部分节点失败 | 节点本地目录不可写或磁盘不足 |
| 应用版本不一致 | 上传资源被缓存或时间戳不匹配 |
| 文件找不到 | 命令路径和本地化后的工作目录不一致 |
这次验证命令里执行了 pwd 和 ls -la,就是为了验证 Container 工作目录中能看到本地化后的资源和运行时文件。实际业务应用也应该保留类似可诊断信息,至少在 debug 模式下能输出本地目录、环境变量和关键文件。
1.7 Container 日志属于运行现场
Container 日志先落在 NodeManager 本地目录,再由日志聚合收集到 HDFS。完成后可以用 yarn logs 查看:
yarn logs -applicationId application_1740547572690_0001
这次日志里能看到两个普通 Container 都执行了命令:
Container: container_1740547572690_0001_01_000002
YARN_AM_CONTAINER_20250226
Container: container_1740547572690_0001_01_000003
YARN_AM_CONTAINER_20250226
这说明 AM 至少完成了三件事:
- 从 RM 拿到了两个普通 Container。
- 向对应 NodeManager 发起了启动请求。
- 两个 Container 都执行了 shell 命令并产生日志。
如果日志聚合失败或还没完成,应用本身仍可能成功,但排查能力会下降。生产环境里不能只看 Final-State=SUCCEEDED,还要看 Log Aggregation Status。日志聚合存在延迟,刚结束的应用可能需要等下一轮聚合后才能通过 yarn logs 查全。没有日志,成功只能证明结果,不能解释过程。
1.8 AM 要监控 Container 状态
启动 Container 只是第一步。AM 还要持续从 RM 获取 Container 完成状态。每个普通 Container 结束后,AM 要判断它是成功、失败、被杀掉,还是需要重试。
Distributed-Shell 的判断相对简单:指定数量的 Container 都成功,应用就成功;只要有必要 Container 失败,应用就可能失败。MapReduce 更复杂,Map Task、Reduce Task、TaskAttempt、Shuffle、OutputCommitter 都会参与状态判断。
但底层模式是一致的:
AM 申请 Container
RM 分配 Container
AM 请求 NM 启动 Container
NM 执行命令
Container 结束
AM 收到完成状态
AM 判断应用是否完成
AM 向 RM 注销
这条链路能解释很多状态问题。应用 RUNNING 只说明 AM 已启动并注册,不代表所有 Container 都正常;普通 Container 全部结束也不代表 AM 已经完成注销;AM 成功注销后,RM 才能把应用标记为最终状态。FINISHED 是 YARN 生命周期状态,不等于业务成功,最终还要看 Final-State 是否为 SUCCEEDED。
2. 失败语义和状态机
2.1 失败时要先判断失败层次
ApplicationMaster 和 Container 启动相关问题,可以按层次拆:
| 层次 | 典型现象 | 排查入口 |
|---|---|---|
| RM / 调度层 | 应用停在 ACCEPTED | Scheduler、队列、AM 资源、AM 资源比例 |
| AM 控制层 | 应用快速 FAILED | AM Container 日志、AM attempt |
| NM 执行层 | AM 正常但普通 Container 启动失败 | NodeManager 日志、Container stderr |
| Container / 业务层 | Container 有退出码 | 用户命令、环境变量、本地文件 |
| 日志不可见 | 状态成功但查不到日志 | 日志聚合、HDFS 权限 |
不要把这些都归为“YARN 有问题”。YARN 只提供资源和执行框架,最终启动的命令、依赖的文件、环境变量是否正确,仍然是应用开发者要负责的部分。
2.2 AM 的状态机比代码入口更重要
写 ApplicationMaster 时,很多人会先找 main 方法、RPC 客户端和回调处理。真正落到工程上,更重要的是状态机。AM 至少要回答几个问题:
- 应用总共需要多少个 Container。
- 已经申请了多少个。
- 已经分配了多少个。
- 已经启动了多少个。
- 成功完成了多少个。
- 失败了多少个。
- 什么条件下应用成功。
- 什么条件下应用失败。
Distributed-Shell 的逻辑很简单:期望数量的 Container 都完成,应用就成功;失败数量超过阈值,应用就失败。MapReduce、Spark、Flink 会复杂很多,还会加入超时处理、重试策略、推测执行和框架自己的任务状态,但底层仍然绕不开这些计数和状态转换。
如果 AM 状态机设计不清楚,最容易出现两类问题。第一类是资源泄漏:Container 已经失败或完成,但 AM 没有释放或没有更新状态,应用一直挂着。第二类是误报成功:部分任务失败了,但 AM 没有正确处理完成状态,最终向 RM 汇报 SUCCEEDED。这两类问题都比普通命令失败更隐蔽。
因此,自定义 YARN 应用里,AM 日志应该重点记录状态变化,而不是只记录“启动了某个 Container”。至少要记录:
requested containers
allocated containers
launched containers
completed containers
failed containers
final application status
有了这些信息,应用运行异常时才能判断是资源没分到、NM 启动失败、命令执行失败,还是 AM 自己的状态判断有问题。
2.3 ContainerLaunchContext 需要可审计
ContainerLaunchContext 是启动 Container 的说明书。它里面的 LocalResource、环境变量和命令,决定了 NodeManager 最终会怎么创建进程。问题是,这个对象在代码里构造完成以后,如果没有日志记录,失败时很难还原。
我更倾向于在 AM 日志里记录一份脱敏后的启动摘要:
| 内容 | 记录方式 |
|---|---|
| Container ID | 完整记录 |
| NodeManager | 完整记录 |
| LocalResource 名称 | 完整记录 |
| LocalResource 路径 | 可按敏感级别脱敏 |
| 环境变量 key | 记录 key,敏感 value 脱敏 |
| 启动命令 | 记录命令模板,敏感参数脱敏 |
| 资源请求 | 完整记录 |
这不是为了增加日志量,而是为了可审计。Container 启动失败时,NodeManager 日志通常能看到最终脚本,但 AM 侧也应该能说明自己发出了什么启动请求。否则很难判断是 AM 构造错了,还是 NM 本地化或执行阶段出了问题。
这次测试里,日志聚合能看到命令被包装成:
exec /bin/bash -c "echo YARN_AM_CONTAINER_20250226; hostname; pwd; ls -la; printenv | sort | head -20; sleep 8 ..."
这类信息对排查很有帮助。它让我们确认用户命令不是在客户端执行,而是在普通 Container 中执行;stdout/stderr 也被重定向到 YARN 日志目录。
2.4 本地化目录解释了很多“找不到文件”
Container 启动时,NodeManager 会创建应用目录和 Container 目录,把 LocalResource 本地化进去,必要时解压归档,然后生成启动脚本。用户命令运行时看到的是 Container 的工作目录,不是客户端提交命令时的目录。
这解释了很多“本地能跑,YARN 上找不到文件”的问题。客户端机器上存在 /opt/app/config.yaml,不代表 NodeManager 上的 Container 能看到它。除非这个文件已经作为 LocalResource 上传并本地化,或者每台节点都预装了相同路径。
在生产环境里,我会尽量避免依赖节点本地固定路径,除非它是明确的基础运行时,例如 JDK、Hadoop 客户端、系统工具。业务 jar、脚本、配置、模型文件应该通过 LocalResource 分发。这样做虽然多一步上传,但能减少节点差异。
如果必须依赖节点本地路径,就要把它纳入节点基线检查:
- 路径是否存在。
- 权限是否一致。
- 版本是否一致。
- 磁盘是否可读。
- 是否在所有 NodeManager 节点存在。
单机环境不会暴露这些差异,多节点集群会非常明显。某个任务只在少数节点失败,很多时候不是 YARN 调度问题,而是节点本地环境不一致。
2.5 AM 与 NM 的失败语义不同
ApplicationMaster 和 NodeManager 都可能导致应用失败,但语义不同。
AM 失败通常意味着应用控制器出了问题。RM 可以根据最大尝试次数重启 AM attempt,但如果 AM 代码或配置本身有问题,重试只会重复失败。AM 失败时要看 AM Container 日志、LocalResource、AM 启动命令和凭据。
NM 侧失败更多发生在普通 Container 启动或运行时。比如本地化失败、启动脚本失败、命令退出码非 0、内存超限、磁盘问题。AM 需要接收这些完成状态,再决定是否重试或终止应用。
二者的区别可以这样看:
| 失败位置 | 影响 | 常见处理 |
|---|---|---|
| AM Container 失败 | 应用控制器不可用 | RM 重试 AM 或应用失败 |
| 普通 Container 失败 | 某个执行单元失败 | AM 决定重试或失败 |
| NM 节点异常 | 该节点上的 Container 受影响 | RM/NM 心跳超时后重新调度 |
| 日志聚合失败 | 结果可能成功但证据不完整 | 修复日志配置或 HDFS 权限 |
排查时要先看失败的是 AM 还是普通 Container。AM 是应用大脑,普通 Container 是执行单元。大脑没启动和某个执行单元失败,不应该用同一种处理方式。
3. AM 设计和资源治理
3.1 自定义 AM 是否值得写
YARN API 给了很强的控制力,但不是所有场景都值得写自定义 AM。写自定义 AM 意味着要自己处理资源申请、启动、失败、日志、状态、清理和安全。对于大多数批处理、流处理、SQL、机器学习任务,更合理的选择是使用已有框架。
适合考虑自定义 AM 的场景通常有几个特征:
| 场景 | 说明 |
|---|---|
| 需要管理长期服务 | 比如一组服务实例,需要按 Container 部署 |
| 需要特殊资源编排 | 普通计算框架不适合表达资源关系 |
| 需要框架级控制 | 要自己定义任务调度、重试和状态 |
| 需要和 YARN 深度集成 | 队列、资源、日志、生命周期都要接入 |
如果只是想批量执行脚本,Distributed-Shell 或调度平台通常已经够用。如果只是跑计算任务,MapReduce、Spark、Flink 更合适。自定义 AM 的价值在于框架级能力,而不是替代 shell 脚本。
3.2 AM 要处理资源释放和清理
应用成功或失败以后,AM 不能只退出进程。它应该明确完成清理和注销。最基本的是向 ResourceManager 发送完成请求,告诉 RM 这个应用最终是成功、失败还是被终止。否则 RM 只能依赖心跳超时或进程退出推断状态。
清理还包括几类资源:
| 资源 | 清理方式 |
|---|---|
| 未使用的 Container 请求 | 向 RM 释放或停止继续申请 |
| 已分配但不再需要的 Container | 通知 NM 停止 |
| 临时状态 | 删除 AM 自己创建的临时文件 |
| 输出目录 | 成功提交或失败回滚 |
| 业务服务 | 停止对外暴露的端口或进程 |
Distributed-Shell 很简单,命令执行完就可以结束。真正的服务型应用更复杂。比如一个 AM 管理多个长期运行的服务实例,应用失败时要不要保留已经运行的 Container,是否允许新 AM attempt 接管旧 Container,这些都要提前设计。
清理逻辑还有一个要求:幂等。AM 可能失败重启,清理动作可能执行一半。删除临时文件、释放资源、提交输出都要能承受重复执行。否则一次 AM 重试就可能造成残留目录、重复输出或状态错乱。
3.3 AM 的心跳决定状态可见性
ApplicationMaster 和 ResourceManager 之间不是持续同步的强一致关系。AM 通过心跳向 RM 申请资源、释放资源、接收分配和上报进度。心跳间隔会影响状态变化被 RM 感知的速度。
这能解释一些看似奇怪的页面现象:
| 现象 | 可能解释 |
|---|---|
| Container 已结束,页面还显示运行中 | 状态尚未通过心跳同步 |
| AM 已申请资源,但暂时没有分配 | 调度器还没有可用资源或下一轮调度未发生 |
| 应用失败后诊断信息滞后 | AM/NM/RM 状态同步存在时间差 |
| 日志已经聚合,但页面仍显示旧链接 | Web UI 展示和日志服务状态不同步 |
这不是说页面不可靠,而是要理解页面是状态投影。YARN 的实际状态分布在 RM、NM、AM 和 HDFS 日志里。排查时如果看到短暂不一致,不要立刻下结论,要结合命令和日志确认。
AM 如果要给用户展示进度,也应该通过心跳或协议上报。MapReduce 能显示 Map/Reduce 进度,是因为 MRAppMaster 持续维护并上报作业内部状态。Distributed-Shell 的进度很简单,复杂框架则需要自己设计进度模型。
3.4 Container 启动命令要减少环境假设
Container 运行在 NodeManager 管理的环境里,不等同于用户登录 shell。启动命令应该尽量减少对交互式环境的假设。
常见问题包括:
- 依赖 .bashrc 或 .zshrc 中的变量。
- 依赖当前目录下的文件。
- 使用节点上不一定存在的命令。
- 假设 PATH 包含业务目录。
- 在命令里写复杂引号和括号。
- 忘记 stdout/stderr 会被重定向。
更稳妥的做法是在启动命令里显式设置必要环境,或者通过 LocalResource 分发脚本,由脚本自己做检查。比如先打印 pwd、hostname、关键环境变量和依赖文件列表。调试阶段多输出几行,通常能节省很多排查时间。
这次验证命令里包含 pwd、ls -la、printenv | sort | head -20,目的就是确认 Container 看到的运行环境。生产应用不一定要保留这么多输出,但 debug 开关应该具备类似能力。
3.5 资源申请不是一次性动作
ApplicationMaster 申请 Container 时,很容易被理解成“告诉 RM 我要几个 Container”。实际运行里更接近一个持续协商过程。AM 会根据应用进度、失败情况和队列资源,不断向 RM 发送请求,也会不断收到分配结果。资源申请、Container 启动、Container 完成这三件事交错发生。
这带来一个重要结论:AM 不能假设所有资源会一次性到位。队列资源紧张时,应用可能长时间停在 ACCEPTED 或只有 AM 在运行;部分 Container 启动后,后续 Container 还在等待;节点异常时,已经分配的 Container 也可能启动失败。AM 的状态机必须能处理这些中间状态。
可以把 AM 内部状态拆成几类:
| 状态 | AM 要做的事 |
|---|---|
| 已注册 | 初始化应用上下文,开始申请资源 |
| 等待分配 | 持续心跳,检查是否超时 |
| 已分配 | 联系 NM 启动 Container |
| 运行中 | 跟踪 Container 状态和进度 |
| 部分失败 | 判断是否重试或整体失败 |
| 完成中 | 停止多余 Container,注销应用 |
MapReduce 的 MRAppMaster 比 Distributed-Shell 复杂很多,原因就在这里。它不仅要申请 Container,还要维护 map task、reduce task、attempt、shuffle、commit 等状态。YARN 只提供资源和进程生命周期,应用语义由 AM 自己承担。
3.6 Container 资源和进程资源要对齐
YARN 分配给 Container 的资源,不会自动等于进程内部真正使用的资源。Container 内存是 YARN 看到的边界,JVM 堆、进程堆外内存、子进程、shell 命令都在这个边界里消耗资源。AM 构造启动命令时,必须让这些资源语义对齐。
常见错误是 Container 申请 1024MB,但启动命令给 JVM 配了 -Xmx1024m。这样看似刚好,实际上没有给 Metaspace、线程栈、Native 内存和其它进程留下空间,很容易触发内存超限。更合理的做法是 Container 内存大于 JVM 堆,并留出一定余量。
CPU 也类似。YARN 的 vcore 是调度维度,不等于操作系统强制隔离的全部细节。应用如果在一个 Container 里启动多个高 CPU 线程,可能会影响同节点其它任务。生产环境要结合 cgroups、NodeManager 配置和框架参数一起治理。
这也是为什么 AM 不能只负责“启动命令”。它还要理解自己启动的是什么进程、需要多少资源、失败后是否重试、重试是否会放大资源压力。Container 是调度单位,业务进程是执行实体,两者之间需要 AM 做准确映射。
4. 排查和工程化落点
4.1 日志目录是 AM 排查的主线
YARN 应用失败后,第一反应不应该是登录节点翻随机目录,而应该沿着日志聚合链路定位。一个 Application 会有 AM Container 日志,也会有普通 Container 日志。AM 日志回答“控制器为什么失败或做了什么决策”,普通 Container 日志回答“执行命令为什么失败”。
排查顺序通常是:
- 看 RM Application 的 diagnostics。
- 看 AM Container 的 stdout/stderr/syslog。
- 看失败普通 Container 的 stdout/stderr/syslog。
- 对照 AM 记录的资源申请和启动命令。
- 必要时再查 NodeManager 本地日志。
如果日志聚合不可用,排查会困难很多。应用结束后,Container 本地日志可能被清理,用户也不一定能登录对应节点。对于长期使用 YARN 的集群,日志聚合不是锦上添花,而是基础运维能力。
Distributed-Shell 的输出虽然很简单,但它展示了日志链路的完整性。普通 Container 输出的 YARN_AM_CONTAINER_20250226 能通过 yarn logs 查到,说明命令确实在 YARN Container 中执行,而不是只在 Client 端打印。
4.2 AM 设计要避免把所有逻辑塞进一个线程
一个可靠的 AM 通常不会把所有事情写在一个顺序流程里。注册 RM、申请资源、处理分配、启动 Container、监听完成、更新进度、处理超时、响应停止请求,这些动作都可能交错出现。简单示例可以顺序写,真正框架需要事件驱动或清晰的状态机。
如果 AM 只是一个大循环,后续会遇到几个问题:
| 问题 | 后果 |
|---|---|
| 心跳被业务逻辑阻塞 | RM 认为 AM 不活跃 |
| 启动 Container 串行过慢 | 资源分配后不能及时使用 |
| 完成事件处理滞后 | 失败重试不及时 |
| 清理逻辑分散 | 应用结束后留下资源残留 |
因此 AM 代码里要把“和 RM 通信”“和 NM 通信”“业务状态管理”“日志和指标”分开。YARN API 提供的是底层能力,工程质量取决于 AM 怎么组织这些能力。
5. 小结
ApplicationMaster 是 YARN 应用运行时的核心。它向 ResourceManager 注册,申请普通 Container,处理分配结果,联系 NodeManager 启动进程,监控 Container 完成状态,最后向 RM 汇报应用结果。
可以把这套模型压缩成五个角色:RM 是集群级 scheduler 和 allocator,NM 是节点级 executor,AM 是应用级 coordinator,Container 是运行时资源单元,Task 或用户进程才是真正的业务执行单元。
理解 AM 后,Container 也不再只是“一个资源单位”。它有本地资源、环境变量、启动命令、工作目录、日志和完成状态。YARN 把这些细节统一成可调度、可监控、可聚合的运行模型。应用开发者真正要做的,是把自己的业务逻辑放进这套模型里,并在失败时能说清楚问题发生在哪一层。