4.3 YARN应用开发部署与扩展


文档摘要

4.3 YARN 应用开发与扩展 本节摘要:任何程序只要实现 AppMaster 协议就能成为 YARN 应用。本节走一遍自研 Application 的骨架(客户端提交、AppMaster 生命周期、容器启动脚本),梳理分布式 shell 的经典实现思路,并扩展到节点标签调度、资源隔离与日志聚合等生产级能力。 自研应用的三件套 写一个跑在 YARN 上的应用,本质是写三段代码:客户端(提交应用、拿 ApplicationId)、AppMaster(协商容器、指挥任务)、容器启动脚本(在 NodeManager 上真正执行的命令)。Hadoop 源码里的 DistributedShell 是官方教学范例——它把一条 shell 命令分发到 N 个容器并行执行,麻雀虽小五脏俱全。

4.3 YARN 应用开发与扩展

本节摘要:任何程序只要实现 AppMaster 协议就能成为 YARN 应用。本节走一遍自研 Application 的骨架(客户端提交、AppMaster 生命周期、容器启动脚本),梳理分布式 shell 的经典实现思路,并扩展到节点标签调度、资源隔离与日志聚合等生产级能力。

自研应用的三件套

写一个跑在 YARN 上的应用,本质是写三段代码:客户端(提交应用、拿 ApplicationId)、AppMaster(协商容器、指挥任务)、容器启动脚本(在 NodeManager 上真正执行的命令)。Hadoop 源码里的 DistributedShell 是官方教学范例——它把一条 shell 命令分发到 N 个容器并行执行,麻雀虽小五脏俱全。

客户端骨架

YarnClient client = YarnClient.createYarnClient(); client.init(conf); client.start(); // 1 向RM登记 拿到全局唯一应用ID ApplicationId appId = client.createApplication() .getNewApplicationResponse().getApplicationId(); // 2 打包运行所需资源到HDFS(jar、脚本、配置) Path dst = new Path("/apps/dshell/" + appId + "/appmaster.jar"); fs.copyFromLocalFile(new Path("appmaster.jar"), dst); // 3 组装首个容器描述:AppMaster本人的规格 ContainerLaunchContext amCtx = Records.newRecord(ContainerLaunchContext.class); amCtx.setCommands(Collections.singletonList( "$JAVA_HOME/bin/java AppMaster " + appId + " 1>" + ApplicationConstants.LOG_DIR_EXPANSION_VAR + "/stdout " + "2>" + ApplicationConstants.LOG_DIR_EXPANSION_VAR + "/stderr")); // 4 提交 client.submitApplication(appContext);

AppMaster 的生命周期是最有意思的部分。它自己就运行在一个容器里,启动后要做的第一件事是向 RM 注册(拿到属于自己的调度面谈资格),然后进入主循环:

AMRMClient<ContainerRequest> rmClient = AMRMClient.createAMRMClient(); rmClient.init(conf); rmClient.start(); rmClient.registerApplicationMaster("am-host", 0, "tracking-url"); // 发起资源请求:5个容器 每个2GB/1核 带优先级 for (int i = 0; i < 5; i++) rmClient.addContainerRequest(new ContainerRequest( Resource.newInstance(2048, 1), null, null, Priority.newInstance(0))); while (finishedContainers < total) { AllocateResponse resp = rmClient.allocate(0.1f); // 心跳:领分配+汇报进度 for (Container c : resp.getAllocatedContainers()) { // 拿到容器 → 与对应NodeManager通信启动任务 nmClient.startContainer(c, buildLaunchContext(c)); } for (ContainerStatus s : resp.getCompletedContainersStatuses()) { finishedContainers++; if (s.getExitStatus() != 0) { /* 重试或失败处理 */ } } } rmClient.unregisterApplicationMaster( FinalApplicationStatus.SUCCEEDED, "done", null);

allocate 这个心跳调用是整个协议的心脏:一次调用同时完成领新容器、收已完成容器状态、上报应用进度三件事。RM 只在这个通道上与你对话——这与 4.1 的握手图完全对应。

容器启动脚本没有魔法:NodeManager 只是在指定目录写一个 shell 脚本并执行它,环境变量与令牌由框架注入。启动 Docker 化任务(Hadoop 3.x 起)也是同一原理,脚本换成 docker run ... 即可。

提交、观测与诊断

应用提交后的标准观测链:

yarn application -list -appTypes DISTRIBUTE_SHELL # Application-Id State Queue Progress # application_17... RUNNING etl 55% yarn application -status application_1724000000000_0009 # 关键字段:Queue / Final-State / Aggregate Resource Allocation # 后者是累计"容器内存GB × 秒" 即这个应用花的资源账单 yarn logs -applicationId application_1724000000000_0009 # 聚合日志:AppMaster与全部容器的stdout/stderr一次拉齐

yarn logs 依赖日志聚合(第 6 章监控的基础设施):容器结束后 NodeManager 把本地日志上传到 HDFS,RM 侧保留索引。没有它,诊断一个跑过 500 台机器的作业要挨台翻节点——开着它,是生产集群的基本卫生习惯。

诊断卡死应用时按状态分流:ACCEPTED 不动是队列排队或 AM 份额满(回看 4.2);RUNNING 但 Progress 为 0 多半是 AppMaster 在等容器或容器内任务死锁;FAILED 看 ExitStatus 与 stderr。资源超限杀(4.1 的内存硬限)在日志里特征明显:Container killed on request. Exit code is 143,伴随 beyond physical memory limits 字样——第一反应永远是核对任务内存配置与数据量,而不是盲目调大 NM 可分配内存。

节点标签:让资源带属性

真实集群常有异构节点:带 SSD 的高配机、大内存机型、或专门给 HBase 的 RegionServer 占用的机器。节点标签(Node Label)让调度器"认属性":

yarn rmadmin -addToClusterNodeLabels "ssd,highmem" yarn node -replaceLabelsOnNodes "node[1-10].example.com=ssd"

队列侧声明可访问的标签与配额:

<property> <name>yarn.scheduler.capacity.root.etl.accessible-node-labels</name> <value>ssd</value> </property> <property> <name>yarn.scheduler.capacity.root.etl.accessible-node-labels.ssd.capacity</name> <value>50</value> </property>

两个高价值用法:计算存储分离——给 HBase 节点打标签且只允许 HBase 队列访问,避免批处理任务与在线查询抢内存(这是 HBase 社区强烈建议的部署形态);异构硬件定向——Shuffle 重、IO 密集的作业定向 SSD 标签。注意标签默认"独占"(打标节点不再接受无标签请求),开放分区与否要想清楚再动。

资源隔离与 vCore 的真相

4.1 提过内存硬限 CPU 软限,这里补全工程细节。内存隔离靠进程树监控:NodeManager 周期检查容器进程树(含子进程)的物理内存与虚拟内存,超限即杀。虚拟内存检查(pmem_vmem_ratio 默认 2.1)在大内存页或本地库多见的负载上常误杀,生产上普遍把它禁掉,只留物理内存检查——这是几乎所有集群的标配调优。CPU 用 cgroups 按 vCore 加权分配 CPU 时间片,超用不死但被压速。理解这对不对称后,容量规划的心法很简单:内存按峰值配、CPU 按均值配

从自研到生态:一个视角收束

写完 DistributedShell 再回头看生态组件,你会发现 Hive on Tez、Spark、HBase 都在重复这套三件套:客户端提交、AppMaster(Spark 叫 Driver,Tez 叫 Session)协商容器、容器跑任务。YARN 把"成为一个分布式框架"的门槛从"造全套轮子"降到了"实现一份协议"——这正是 4.1 分层手术的红利兑现处。第 5 章讲每个生态组件时,请带着这个视角:它们只是数据旅程不同站点上的 YARN 应用而已。

本节要点回顾

  • 三件套结构:客户端提交与上传资源、AppMaster 注册加心跳循环、容器脚本执行;allocate 心跳一次完成领容器收状态报进度;
  • 日志聚合必开:yarn logs 一次拉全 AppMaster 与容器日志,是排障第一入口;
  • 状态分流诊断:ACCEPTED 查队列与 AM 份额,RUNNING 查容器与任务,ExitCode 143 先核内存配置;
  • 节点标签实现异构调度与计算存储分离,独占语义要谨慎评估;
  • 内存按峰值、CPU 按均值;虚拟内存检查在多数生产集群应禁用;
  • 生态组件都是 YARN 应用:Hive、Spark、HBase 共用同一套三件套协议。

YARN 篇收官。下一章数据进入旅程最后一程的多个出口:生态组件各取所需。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U