Hadoop YARN容量调度器默认配置详解与生产调优实战 1. 从一次集群告警说起为什么默认配置值得单独拎出来讲刚接手一个跑了几十个离线任务的集群那会儿我遇到过一个特别典型的现象白天任务提交上去队列里排得满满当当但集群整体资源利用率却只有三成左右。当时第一反应是资源不够差点就去申请扩容了。后来把capacity-scheduler.xml翻出来逐行看了一遍才发现问题根本不在机器数量上而是默认配置里几个关键参数把资源卡死了。capacity-scheduler.xml是 Hadoop YARN 里 Capacity Scheduler容量调度器的核心配置文件。YARN 作为资源调度层决定了集群里 CPU、内存这些资源怎么分给不同的队列、不同的应用。而 Capacity Scheduler 是 Apache Hadoop 默认启用的调度器它的默认配置直接决定了一个刚装好的集群出厂状态下能跑成什么样。很多人装完集群能跑通 WordCount 就以为万事大吉实际上默认配置是为能跑起来设计的不是为跑得好设计的。这篇内容适合三类人看一是刚接触 YARN、想搞明白调度器到底在管什么的新手二是集群跑着跑着发现资源利用率上不去、任务排队异常、想排查配置问题的运维或开发三是准备做多队列资源隔离、需要从默认配置出发做定制的人。我会把默认配置里每个关键参数拆开讲清楚它默认是什么、为什么这么设、实际生产里应该怎么调再配上我踩过的坑和排查思路。整篇围绕capacity-scheduler.xml这一份文件展开但会把背后的调度逻辑讲透让你看完能直接对着自己的集群动手改。2. 默认配置全貌与设计逻辑拆解2.1 默认文件到底长什么样Hadoop 发行版里自带的capacity-scheduler.xml如果你不去动它核心内容其实非常素。它主要包含这么几类配置根队列的资源分配、默认队列的定义、资源计算器、以及一堆带默认值的开关。我把最关键的几项列出来这也是后面所有讨论的基础。配置项默认值作用yarn.scheduler.capacity.root.queuesdefault根队列下只有一个 default 子队列yarn.scheduler.capacity.root.default.capacity100default 队列占根队列 100% 容量yarn.scheduler.capacity.root.default.maximum-capacity100default 队列最大可弹性到 100%yarn.scheduler.capacity.root.default.stateRUNNING队列处于运行状态yarn.scheduler.capacity.root.default.user-limit-factor1单用户最多用队列 1 倍容量yarn.scheduler.capacity.resource-calculatorDefaultResourceCalculator只按内存调度忽略 CPUyarn.scheduler.capacity.maximum-am-resource-percent0.1AM 最多占队列 10% 资源yarn.scheduler.capacity.node-locality-delay40本地化等待的调度机会数这张表里每一项都值得单独说但先记住一个整体印象默认配置是单队列、全内存、保守分配的思路。它假设你只有一个队列所有应用挤在一起调度器只关心内存够不够CPU 基本不管。2.2 为什么默认只给一个 default 队列很多人第一次看到root.queuesdefault会疑惑YARN 不是号称支持多队列资源隔离吗怎么默认就一个队列这其实是刻意的设计取舍。Capacity Scheduler 的定位是多租户共享大集群但一个刚装好的集群往往还没确定租户划分方案这时候给你一个 default 队列保证任何应用提交上来都有地方跑不会因为队列没配好直接失败。从调度器实现角度看根队列root是必须存在的顶层容器它下面挂的子队列才是真正分配资源的地方。默认只挂一个 default等于把 100% 资源都给了它。这种设计的好处是零配置可用坏处是所有应用不分优先级、不分用户地抢同一池子资源一个跑飞的大任务能把整个集群拖垮。所以生产环境第一件事往往就是拆队列而拆队列的起点就是理解这个默认结构。提示改root.queues增加子队列时新增队列的 capacity 之和不能超过 100否则 ResourceManager 启动会直接报配置错误。这是新手最容易踩的第一个坑。2.3 DefaultResourceCalculator 带来的隐性限制默认资源计算器是DefaultResourceCalculator它只按内存做调度决策CPU 资源在调度层面被完全忽略。这个默认值在早期 Hadoop 里很常见因为那时候大部分集群是内存瓶颈型CPU 相对富裕。但现在情况变了很多任务比如机器学习训练、视频转码是 CPU 密集型如果调度器不看 CPU就会出现内存分出去了但 CPU 被抢爆的情况任务实际执行效率极低。要改成同时考虑内存和 CPU需要把计算器换成DominantResourceCalculator。这个计算器会取内存和 CPU 中占比更高的那个作为主导资源Dominant Resource按主导资源做公平分配。改这一项的影响面很大它会改变整个集群的调度行为所以不建议在集群跑着大量任务时贸然切换最好在业务低峰期做并且提前压测。2.4 user-limit-factor 与 AM 资源上限的保守哲学user-limit-factor1意味着单个用户最多只能用掉队列 100% 的容量听起来好像没限制但结合多用户场景就有意义了如果队列容量是 100两个用户同时提交任务默认情况下每个用户最多用 100但实际调度会尽量让两人平分。这个参数设成大于 1 的值比如 2允许单用户在队列空闲时超用资源适合平时人少、偶尔爆发的场景。maximum-am-resource-percent0.1限制 ApplicationMaster 最多占用队列 10% 的资源。AM 是每个应用的管理进程本身不干重活但它要占一个 container。如果这个比例设太高大量小应用的 AM 会把资源吃光真正干活的 task 反而没资源设太低并发应用数就上不去。默认 0.1 是个折中值小集群够用大集群跑大量并发应用时经常需要往上调。3. 核心参数逐个拆解与实操调优3.1 队列容量三兄弟capacity、maximum-capacity、user-limit-factor这三个参数是 Capacity Scheduler 的灵魂必须一起理解。capacity是队列的保底份额比如两个队列各 50那不管对方多闲你至少能拿到 50% 的资源。maximum-capacity是弹性上限对方闲着的时候你可以临时超用但最多不超过这个值。user-limit-factor则是在队列内部限制单个用户能占队列容量的倍数。举个具体例子。假设根队列下有两个子队列 A 和 B配置如下property nameyarn.scheduler.capacity.root.queues/name valuea,b/value /property property nameyarn.scheduler.capacity.root.a.capacity/name value60/value /property property nameyarn.scheduler.capacity.root.a.maximum-capacity/name value80/value /property property nameyarn.scheduler.capacity.root.b.capacity/name value40/value /property property nameyarn.scheduler.capacity.root.b.maximum-capacity/name value100/value /property这个配置的含义是A 保底 60%最多能涨到 80%B 保底 40%最多能涨到 100%。当 A 空闲时B 可以吃掉 A 释放的资源一直涨到 100%。注意 A 和 B 的 capacity 之和是 100这是硬性要求而 maximum-capacity 各自可以到 100互不冲突因为它表达的是上限而非份额。实操中我建议 capacity 按业务重要性分配maximum-capacity 留出弹性空间。比如核心业务队列 capacity 给 50、maximum 给 70离线分析队列 capacity 给 30、maximum 给 100这样核心业务有保底离线任务能在空闲时吃满剩余资源整体利用率能明显提升。3.2 资源计算器切换的完整操作与验证把DefaultResourceCalculator换成DominantResourceCalculator只需要改一行配置property nameyarn.scheduler.capacity.resource-calculator/name valueorg.apache.hadoop.yarn.util.resource.DominantResourceCalculator/value /property但改完之后必须验证否则你可能以为生效了其实没有。验证方法有两个一是看 ResourceManager 日志里调度器初始化时打印的计算器类名二是提交一个 CPU 密集任务观察yarn.scheduler相关的指标里 CPU 是否参与了分配决策。我一般用第二种更直观。这里有个容易忽略的点切换计算器后之前按内存算出来的队列使用率会全部重算可能出现某个队列突然超了 maximum-capacity 的情况导致新任务被卡住。所以切换前要先用yarn queue -status看一遍各队列当前的内存和 CPU 使用情况确认切换后不会触发上限。注意DominantResourceCalculator需要 NodeManager 正确上报 CPU 核数yarn.nodemanager.resource.cpu-vcores如果这个值配得不对主导资源计算会失真。默认它等于机器物理核数但很多虚拟化环境需要手动指定。3.3 AM 资源上限与并发应用数的关系maximum-am-resource-percent直接决定了集群能同时跑多少个应用。假设队列总资源是 100GB 内存AM 默认占 1GB那 10% 就是 10GB最多同时跑 10 个应用的 AM。如果你要跑 50 个并发小应用这个默认值就不够了需要往上调比如调到 0.3 甚至 0.5。但调高有代价AM 占的资源多了留给实际 task 的就少了。所以这是个平衡。我的经验是先估算平均每个应用需要多少 AM 内存和多少 task 内存再反推合理的比例。比如每个应用 AM 1GB、task 平均 4GB、平均每个应用 5 个 task那单个应用总需求约 21GB其中 AM 占 1GB 约 5%这时候 0.1 的默认值其实够用。但如果应用普遍很小、task 很少AM 占比就会升高默认值就不够了。另外还有个相关参数yarn.scheduler.capacity.maximum-am-resource-percent是队列级别的可以在子队列上单独覆盖。给跑大量小应用的队列单独调高给跑大任务的队列保持默认这样更精细。3.4 本地化等待node-locality-delay 的取舍node-locality-delay40表示调度器在放弃本地化、退而求其次选择非本地节点之前会等待 40 次调度机会。本地化Locality指的是把 task 分配到数据所在的节点避免跨网络拉数据。等待越久本地化命中率越高但任务启动延迟也越大。默认 40 是个偏保守的值适合数据量大、网络带宽紧张的场景。如果你的集群网络很好比如万兆内网或者任务对启动延迟敏感比如交互式查询可以把这个值调小比如 10 到 20让任务更快跑起来。反过来如果任务是重 IO 的批处理调大一点能减少跨节点数据传输。这个参数没有绝对最优值得结合你的网络和任务特性试。我一般先在默认值下跑一批任务看 ResourceManager 日志里本地化命中的比例如果命中率已经很高比如 90% 以上说明 40 够用甚至偏大如果命中率低可能是数据分布本身就不均匀调参数也救不了得从数据布局入手。4. 完整实操从默认配置到多队列生产配置4.1 改造前的现状盘点动手改之前先摸清现状。我习惯跑这么几条命令用yarn queue -status default看默认队列的资源和使用率用yarn application -list看当前跑着哪些应用、各占多少资源再看 ResourceManager 的 Web UI 里各队列的 pending 和 running container 数。这一步的目的是搞清楚现在的瓶颈到底在哪避免盲目改配置。假设盘下来发现所有应用挤在 default 队列内存利用率 60%但 CPU 利用率只有 20%而且经常有任务因为抢不到 AM 资源而卡在 ACCEPTED 状态。这就说明两个问题一是需要拆队列做隔离二是 AM 资源上限偏低三是 CPU 没被调度器利用起来。4.2 设计队列结构基于上面的现状我设计一个三队列结构prod核心生产、offline离线批处理、adhoc临时查询。prod 保底 40%、上限 60%offline 保底 40%、上限 100%adhoc 保底 20%、上限 50%。这样核心业务有保底离线任务能弹性吃满空闲资源临时查询有独立空间不干扰别人。对应的配置片段property nameyarn.scheduler.capacity.root.queues/name valueprod,offline,adhoc/value /property property nameyarn.scheduler.capacity.root.prod.capacity/name value40/value /property property nameyarn.scheduler.capacity.root.prod.maximum-capacity/name value60/value /property property nameyarn.scheduler.capacity.root.offline.capacity/name value40/value /property property nameyarn.scheduler.capacity.root.offline.maximum-capacity/name value100/value /property property nameyarn.scheduler.capacity.root.adhoc.capacity/name value20/value /property property nameyarn.scheduler.capacity.root.adhoc.maximum-capacity/name value50/value /property注意 capacity 之和 404020100符合要求。每个队列还要设stateRUNNING否则队列不工作。4.3 应用如何指定队列队列建好了应用得知道往哪提交。有几种方式提交时用-D mapreduce.job.queuenameoffline指定或者在mapred-site.xml里配默认队列再或者在代码里通过Job.setQueueName()设置。我一般推荐在提交脚本里显式指定这样最清晰也方便不同任务走不同队列。有个细节如果应用指定的队列不存在或者队列处于 STOPPED 状态任务会直接失败。所以改完队列配置后一定要先重启 ResourceManager或者用yarn rmadmin -refreshQueues热加载再用一个测试任务验证每个队列都能正常提交。4.4 热加载与重启的取舍yarn rmadmin -refreshQueues可以在不重启 ResourceManager 的情况下重新加载队列配置这对生产集群非常友好。但它有个限制只能改队列的容量、上限、状态这些属性不能新增或删除队列。也就是说如果你要加一个新队列还是得重启 ResourceManager。我的做法是日常调容量用 refreshQueues涉及队列增删的改动安排在维护窗口重启。重启前先yarn application -list确认没有关键任务在跑或者用-Dmapreduce.job.queuename把任务临时导到其他队列。提示refreshQueues 之后正在运行的应用不会重新分配资源新配置只对新提交的应用生效。所以调完容量别指望立刻看到变化得等新任务上来。5. 常见问题与排查技巧实录5.1 队列容量之和不等于 100 会怎样这是最高频的配置错误。如果子队列 capacity 之和小于 100ResourceManager 启动时会警告剩余容量会被浪费没有队列认领如果大于 100直接启动失败报IllegalArgumentException。我见过有人把三个队列配成 50、50、50结果 RM 起不来排查半天才发现是加法问题。解决办法很简单确保同一层级下所有子队列的 capacity 之和恰好等于 100。如果确实想留余量可以加一个保留队列占住剩余份额而不是让和小于 100。5.2 任务一直卡在 ACCEPTED 状态任务提交后长时间处于 ACCEPTED说明调度器分配不出资源。常见原因有三个一是队列资源被占满且达到 maximum-capacity二是 AM 资源上限到了新应用的 AM 起不来三是队列 state 是 STOPPED。排查顺序先yarn queue -status 队列名看使用率和上限再看maximum-am-resource-percent是否卡住了 AM最后确认队列状态。我遇到最多的是第二种尤其是跑大量小应用时默认 0.1 的 AM 上限很快就被占满表现就是任务全卡在 ACCEPTED但集群内存明明还有富余。5.3 改了配置不生效配置改了但行为没变通常是这几个原因忘了 refreshQueues 或重启改错了文件集群里可能有多个 capacity-scheduler.xml比如 Hadoop 目录一份、各节点一份配置项名字拼错比如把maximum-capacity写成max-capacity。我的排查习惯是改完先grep确认文件里配置项名字正确再 refreshQueues然后看 ResourceManager 日志里有没有加载新配置的记录。如果日志里没动静八成是文件路径不对或者 RM 没读到。5.4 常见问题速查表现象可能原因排查动作RM 启动失败capacity 之和大于 100检查各子队列 capacity 相加任务卡 ACCEPTED队列满 / AM 上限到 / 队列 STOPPED查队列状态和 AM 使用率配置不生效未 refresh / 文件错 / 名字错grep 配置项 看 RM 日志CPU 利用率低用了 DefaultResourceCalculator切换为 DominantResourceCalculator本地化命中率低node-locality-delay 偏小或数据不均调大 delay 或检查数据分布5.5 几个我踩过的坑第一个坑是user-limit-factor设太大。有次为了让人少的时候能多用资源把它设成了 5结果单个用户提交大量任务时把整个队列吃满其他用户全被饿死。后来改回 1 到 2 之间配合队列隔离才稳定。第二个坑是忽略yarn.scheduler.capacity.node-locality-delay的单位。它不是秒也不是毫秒而是调度机会数跟集群规模和任务提交频率有关。我一开始按时间理解调来调去没效果后来才搞明白它的真实含义。第三个坑是热加载后没验证。有次 refreshQueues 后以为生效了结果新队列根本没加上因为新增队列不支持热加载任务提交上去全失败。从那以后我养成了改完必测的习惯用一个最小任务跑一遍每个队列。6. 默认配置的边界与后续扩展方向把capacity-scheduler.xml的默认配置吃透本质上是在理解 YARN 资源调度的最小可用模型。默认值不是错的它是为通用场景设计的保守起点。真正要让它适配你的业务得从队列结构、资源计算器、AM 上限、本地化策略这几个维度去调而每个维度的调整都要有明确的业务理由不能为了调而调。这套配置后续还能往几个方向扩展一是结合Fair Scheduler做对比选型看哪种调度器更适合你的负载特征二是引入队列的 ACL 控制限制哪些用户能往哪些队列提交三是配合 Node Labels 做更细粒度的资源分区比如把 GPU 节点单独划给特定队列。这些都是在默认配置基础上长出来的能力前提是你先把默认这层搞扎实。我个人在实际操作中的体会是YARN 调优最忌讳一上来就抄网上的最佳配置。别人的队列结构和你的业务负载可能完全不同照搬往往适得其反。更靠谱的做法是先用默认配置跑一段时间收集真实的资源使用数据再基于数据做针对性调整。配置改完一定要验证验证完一定要观察一段时间确认没有副作用再固化下来。这套流程走下来比任何现成的配置模板都管用。