深入 Hadoop 08 - YARN 资源模型、Container 与 NodeLabel
上一篇讲了 YARN 三方契约的整体架构。本篇展开资源模型——Container、Resource、NodeLabel 这些抽象的具体含义。
YARN 的资源模型常被简化成"申请多少内存和 CPU"。这个简化漏了几个关键设计决策:最小分配粒度(避免碎片)、最大分配上限(防止单作业垄断)、多维资源(不只是 CPU/Memory,Hadoop 3.x 起可扩展 GPU/FPGA)、NodeLabel(拓扑约束)、资源超售(Opportunistic Container)。这些机制决定了 YARN 集群的资源利用率和公平性。
本篇只抓一个问题:YARN 怎么把"集群物理资源"切成可调度的 Container 单位,最小分配和最大分配为什么是这样设计的,NodeLabel 怎么让 GPU/HBM/高速网络等异构资源被正确分配,Opportunistic Container 在什么场景下值得打开。
Resource 的两个核心维度
YARN 早期版本(2.x)的 Resource 只有两个维度:
1 | |
memory 是 container 的内存上限,单位是 MB。默认 DefaultContainerExecutor 下,NM 主要通过 ContainersMonitor 轮询物理/虚拟内存并杀死超限进程;启用 LinuxContainerExecutor + cgroups 后,才会通过 cgroups 做更硬的资源边界。
vCores 是 container 的虚拟 CPU 核数。一个 vCore 不等于一个物理核——vCores 是逻辑单位,由 NM 配置决定一节点能提供多少 vCores。一台 32 物理核的服务器可以配置成提供 32 vCores(1:1)或 64 vCores(2:1,假设 CPU 超线程)。vCores 的限制方式取决于配置:
1 | |
默认配置下 vCores 仍是软限制(cpu shares,竞争时按权重分配)。只有启用 LinuxContainerExecutor 并打开严格 cgroups 配置后,才会变成 cgroup cpu quota 这类硬限制。
这种两维模型在 Hadoop 2.x 早期够用,因为那时 YARN 主要承载 MapReduce 和 Spark——它们的资源需求主要是内存和 CPU。GPU 训练、FPGA 加速等场景出现后,两维模型表达不了"我需要 1 块 GPU + 8 GB 内存 + 2 vCores"这种多维需求。
Hadoop 3.x 的可扩展资源类型
Hadoop 3.1.0 引入了可扩展资源类型(YARN-3926)。Resource 不再硬编码只有 memory 和 vCores,而是支持额外的命名资源类型,每种类型有最小粒度。
1 | |
这种设计让 YARN 可以表达"这个节点有 2 块 GPU、128 GB 内存、32 vCores",AM 可以申请"给我 1 块 GPU + 16 GB 内存 + 4 vCores"。RM 调度时按多维资源匹配,找到能同时满足所有维度的节点。
注意 yarn.resource-types 不能重复声明内置的 memory-mb 和 vcores。当前 ResourceTypes 枚举只有 COUNTABLE 一种类型;GPU、FPGA 等资源通过单位和最小/最大 allocation 表达粒度,而不是靠第二种枚举区分连续资源。
GPU 资源还涉及设备访问权限。NM 启动 container 时除了 cgroups 限制,还要把 GPU 设备节点(例如 /dev/nvidia0)的访问权限授予 container 进程。Hadoop 3.1 起支持 NVIDIA GPU 资源隔离:裸进程路径依赖 cgroups devices controller,Docker 路径在官方文档里以 NVIDIA Docker 运行时为前提。
最小分配与最大分配
YARN 调度器(无论 Capacity 还是 Fair)都有一对关键配置:
1 | |
最小分配粒度的设计目的是避免资源碎片。如果一个 100 GB 内存的节点被切成 1000 个 100 MB 的 container,调度器要花大量计算判断"剩余碎片能否满足新请求"。最小粒度 1 GB 让每个 container 至少占 1 GB,节点上的剩余内存用整数 GB 表达,调度计算大幅简化。
最大分配上限防止单作业垄断。一个作业申请 100 GB container 在 100 GB 节点集群上只能跑到一个节点上,其他节点无法分担,作业吞吐受限。最大上限 8 GB 让单个 container 不会过大,保证作业的并行度。
最小粒度还规定增量的步长。AM 申请 1.5 GB 时调度器向上取整到 2 GB(步长 = 最小粒度 1 GB)。AM 申请 0.5 GB 时调度器向上取整到 1 GB。这种"申请值向上取整到最小粒度倍数"的逻辑让调度器不需要处理任意精度资源。
低于最小 allocation 或不是粒度整数倍的申请会被 RM normalize 到可分配值;只有请求超过最大 allocation 时才会抛出 InvalidResourceRequestException。
DominantResourceCalculator:多维资源的公平比较
Hadoop 3.x 的多资源类型引入一个新问题:怎么判断"作业 A 用 100 GB 内存 + 2 vCores"和"作业 B 用 50 GB 内存 + 10 vCores"哪个占用更多?
答案是用 Dominant Resource Fairness(DRF)算法,由 Ghodsi 等人 2011 年 NSDI 论文提出。DRF 的核心思路是计算每个作业的"主导资源占比"——作业占用的某维度资源 ÷ 集群该维度总量,取最大值。
1 | |
DominantResourceCalculator 是 DRF 在 YARN 的实现。Capacity Scheduler 通过 yarn.scheduler.capacity.resource-calculator 切换 calculator;Fair Scheduler 则在 allocation file 中为队列配置 schedulingPolicy=drf。默认策略只按内存比较,多维资源集群应显式启用 DRF。
NodeLabel:拓扑约束
集群里某些节点有特殊资源(GPU、FPGA、HBM、高速网络)。YARN 的 NodeLabel 把节点划入调度分区:一个节点最多属于一个 partition,AM 可以指定某个 container 必须跑在 GPU 分区。机房、机架等可叠加的键值约束在 Hadoop 3.2+ 更适合用 Node Attribute 表达。
NodeLabel 的两种类型:
独占标签(exclusive label):默认模式。请求 GPU 的 container 只能落在 GPU 分区,请求 DEFAULT 分区的 container 也不能借用 GPU 节点。
非独占标签(non-exclusive label):请求 DEFAULT 分区的 container 可以在该分区空闲时借用节点;显式请求其他 label 的 container 不能泛化地借用。
NodeLabel 的配置和分配:
1 | |
Capacity Scheduler 配置每个队列可访问的 label:
1 | |
提交作业到这个队列时,AM 申请 container 可以显式指定 label:
1 | |
NodeLabel 解决的是分区容量与异构资源调度问题。GPU、FPGA 等互斥分区可以用 label;机房、机架、磁盘类型等多维放置条件应使用 Node Attribute 做过滤,避免把一个节点只能属于一个 partition 的 label 当成通用标签系统。
Opportunistic Container:机会性执行
YARN 传统 guaranteed container 只有在节点有未分配资源时才会启动。Opportunistic container 的目标不是读取 container 的实际 CPU/内存使用量做超售,而是允许 RM 或 NM 先把低优先级 container 派发到 NodeManager;如果当时没有足够资源,它会在 NM 侧排队,等资源释放后再启动。
Hadoop 2.9 引入了 Opportunistic Container(YARN-2877)。它适合短任务和可重试任务:被延后或抢占时,AM 能重新申请 container,不影响作业正确性。它不适合长服务或强状态任务,因为这些任务对启动时机和中断更敏感。
启用 Opportunistic Container:
1 | |
yarn.nodemanager.opportunistic-containers-max-queue-length 默认是 0;不显式调大时,NM 侧不会为 opportunistic container 留排队空间。生产环境里,这类 container 的收益主要来自短任务吞吐和节点本地排队能力;它没有解决“已分配但未实际使用”的资源利用率问题,后者仍属于 YARN-1011 一类的资源超售议题。
实验:观察资源模型
UNVERIFIED_RUNTIME:下面命令和输出形态用于在真实 YARN 集群上核对现象,本轮未连接 live Hadoop 集群运行。
yarn node -list -all 列出 NM;查看单节点资源详情要再运行 yarn node -status <NodeId>:
1 | |
这行输出展示了三个事实:
该节点配置 100 GB 内存 + 32 vCores + 2 GPU(Hadoop 3.x 多资源类型)。
已分配 80 GB + 25 vCores + 1 GPU,剩余 22 GB + 7 vCores + 1 GPU。
该节点有 GPU 标签,只有声明 GPU 的作业能在这里跑。
提交一个 Spark 作业,指定资源:
1 | |
这条命令向 YARN 申请 10 个 executor,每个 executor 用 4 GB 内存 + 2 vCores,且必须跑在 GPU 节点上。RM 调度时按 GPU label 过滤候选节点,找到 10 个能满足的节点。
提交后用 yarn application -status <appId> 观察:
1 | |
每个 container 的资源量直接反映 spark-submit 的配置。
模式提炼
YARN 资源模型体现的设计模式:
1 | |
这个模式不只是 YARN。Mesos 的资源模型(scalar resources)类似,但更简洁——默认只有 cpu / mem / disk / ports 四维,可扩展但不像 YARN 那么 generic。Borg 用 dimensions(cpu / ram / disk)+ constraints(机器属性)两套机制,比 YARN 更早引入多维资源。Kubernetes 的 Resource Model(requests + limits + nodeSelector / nodeAffinity)是 YARN Resource + NodeLabel 的对照。
数据库领域的对应物是"资源池"。Oracle / DB2 的 Resource Manager 配置每个工作负载的资源上限,与 YARN 的 Capacity Scheduler 队列模型类似。差别是数据库资源池通常只管 CPU 和 IOPS,不管内存(数据库内存是 buffer pool,由 DBMS 自己管)。
工程迁移表
| YARN 概念 | Mesos | Kubernetes | Borg | 数据库资源池 |
|---|---|---|---|---|
| Resource(多维向量) | Resources | Resource Requirements | dimensions | workload resource |
| 最小粒度 | 默认 1MB / 0.1 CPU | 任意(通过 requests) | 0.001 CPU | database allocation |
| 最大上限 | quota | LimitRange | max alloc | pool size |
| NodeLabel | attributes | nodeSelector / nodeAffinity | constraints | workload group |
| Dominant Resource Fairness | DRF(Mesos 原生) | priority + preemption | fairness | DBMS priority |
| Opportunistic Container | reclaimable | BestEffort QoS | best-effort | - |
| cgroups 强制 | 是 | cgroups + namespace | 是 | OS priority |
注意 Kubernetes 这一列的差异。Kubernetes 用 requests 和 limits 两套值——requests 是调度依据(scheduler 按 requests 找节点),limits 是运行时上限(kubelet 按 limits 设置 cgroups)。YARN 只有"申请值",调度和运行时都用这个值。这让 YARN 比 Kubernetes 简单,但也少了"申请 4G 实际用 8G"的灵活性。Kubernetes BestEffort QoS 是 YARN Opportunistic Container 的对应物——在节点资源紧张时被优先抢占。
常见误解
误解一:“vCores 就是物理 CPU 核数”。vCores 是逻辑单位,由 NM 配置决定。一台 32 物理核 + 超线程(64 硬件线程)的服务器,可以配置成提供 32 vCores、64 vCores、甚至 128 vCores。配置多少取决于超售策略——vCores 配得高(超售)让集群看起来资源多,但 container 之间 CPU 争抢严重;配得低(保守)让容器内 CPU 充足,但集群资源利用率低。生产环境通常按 1:1(每物理核 1 vCore)或 2:1(含超线程)配置。
误解二:“申请的资源就是 container 能用的资源”。默认 DefaultContainerExecutor 由 NodeManager 监控内存并在超限后终止进程,CPU 不由 cgroups 硬限制。即使启用 LinuxContainerExecutor,yarn.nodemanager.linux-container-executor.cgroups.strict-resource-usage 默认也是 false;只有显式开启后 CPU 才按 vCores 设硬上限。
误解三:“NodeLabel 是任意节点标签”。NodeLabel 的官方语义是 capacity partition,一个节点最多属于一个 partition。Hadoop 3.2+ 的 Node Attribute 才适合同时表达机架、机房、磁盘类型等多组键值约束。
误解四:“Opportunistic Container 是免费午餐”。Opportunistic Container 让集群利用率提升,代价是 container 可能被抢占。AM 必须能处理抢占(重试 task),不适合长服务。如果作业的所有 task 都被频繁抢占,整体吞吐反而下降——重新调度 task 的开销可能超过 opportunistic container 的资源收益。
误解五:“最大 allocation 越大越好”。最大 allocation 上限决定单个 container 能申请多少资源。设得过大(比如 100 GB)让单个 container 可以占满整个节点,其他作业的 task 必须等这个 container 释放。设得过小(比如 2 GB)让大型 Spark executor 拆成过多小 container,调度开销和 RPC 开销上升。常见值在 8-16 GB 之间,取决于集群规模和典型作业大小。
练习
-
在 Hadoop 集群运行
yarn node -list -all,再对一个 NodeId 运行yarn node -status <NodeId>,观察资源信息。如果集群配置了yarn.resource-types,再观察扩展资源维度。 -
提交一个 Spark 作业,用不同
--executor-memory和--executor-cores配置,观察 RM Web UI 上每个 container 的资源量。把 memory 设为非最小粒度倍数(例如 1500 MB),观察 RM 如何向上 normalize;再申请超过 maximum allocation 的值,观察异常。 -
在
apache/hadoop源码里找到Resource.java、NodeLabel.java(hadoop-yarn-api模块)以及DominantResourceCalculator.java(hadoop-yarn-common模块),观察多维资源比较和 NodeLabel 的核心实现。 -
思考题:如果让 YARN 的最小分配粒度变成 1 MB(而不是默认 1024 MB),调度器的计算复杂度会怎么变化?集群行为会有什么改变?为什么 Hadoop 默认选 1 GB?
系列导航
参考资料
- Ali Ghodsi, Matei Zaharia, et al. Dominant Resource Fairness: Fair Allocation of Multiple Resource Types. NSDI 2011.(DRF 算法原始论文,YARN DominantResourceCalculator 的理论基础)
- Vinod Kumar Vavilapalli et al. Apache Hadoop YARN: Yet Another Resource Negotiator. SOCC 2013. Section 4.3 “Resource Model” 详细描述了 YARN 2.x 时代的两维资源模型。
- Apache Hadoop 官方文档:YARN Resource Model. https://hadoop.apache.org/docs/r3.4.1/hadoop-yarn/hadoop-yarn-site/ResourceModel.html
- Apache Hadoop 官方文档:Node Labels. https://hadoop.apache.org/docs/r3.4.1/hadoop-yarn/hadoop-yarn-site/NodeLabel.html
- Apache Hadoop 官方文档:Opportunistic Containers. https://hadoop.apache.org/docs/r3.4.1/hadoop-yarn/hadoop-yarn-site/OpportunisticContainers.html
- Apache Hadoop 源码:
Resource.java、ResourceTypeInfo.java、DominantResourceCalculator.java. https://github.com/apache/hadoop
