YARN容量调度器capacity-scheduler.xml默认配置详解与生产调优指南 1. 从一个被忽略的文件说起capacity-scheduler.xml 到底管什么如果你在维护一套跑在 YARN 上的大数据集群大概率遇到过这样的场景半夜收到告警某个离线任务把队列资源吃满导致实时任务排队几十分钟或者两个部门共用一套集群谁都不肯让资源最后变成“先到先得”的混战。这些问题追根溯源十有八九会落到同一个文件上——capacity-scheduler.xml。这个文件是 YARN 容量调度器Capacity Scheduler的核心配置文件。它决定了集群的资源怎么划分成队列、每个队列能拿多少资源、队列之间能不能互相借资源、单个任务最多能占多少。说白了它就是集群资源的“分蛋糕规则”。默认配置是 Hadoop 出厂自带的一套保守参数能跑起来但几乎不可能直接适配生产环境。很多人装完集群后压根没动过这个文件结果就是所有任务挤在一个 default 队列里互相抢资源利用率低、任务互相干扰、优先级形同虚设。这篇内容适合三类人看一是刚接手 YARN 集群、需要搞清楚调度逻辑的运维和开发二是正在做多租户资源隔离、被队列配置折磨的工程师三是想从默认配置入手理解容量调度器设计思路的学习者。我会把默认配置逐项拆开讲清楚每个参数背后的意图再结合常见的生产调整场景给出可以直接参考的配置思路。全程围绕capacity-scheduler.xml这一个文件展开不跑偏。先说一个基本认知容量调度器的设计目标是“保证每个队列的最低资源同时允许空闲资源被其他队列借用”。这个“保证 借用”的双重特性是理解所有参数的关键。默认配置里这个特性体现得非常克制甚至可以说是“几乎不启用”。下面我们一层层剥开。2. 默认配置逐项拆解每个参数为什么长这样2.1 队列结构为什么默认只有一个 default 队列打开capacity-scheduler.xml最先看到的是队列定义相关的配置。默认情况下文件里其实只有寥寥几行有效配置队列结构靠的是代码里的兜底逻辑。核心参数是property nameyarn.scheduler.capacity.root.queues/name valuedefault/value /property这行的意思是根队列root下面只有一个子队列叫default。所有提交的任务如果没有指定队列都会进这个 default 队列。为什么默认这么设计因为 Hadoop 要保证“开箱即用”。如果默认就搞出一堆队列用户提交任务时还得先搞清楚该往哪个队列放门槛太高。所以出厂设置就是“一个队列装所有”简单粗暴但能用。问题在于单队列意味着没有任何隔离。一个跑机器学习训练的任务和一个跑日常 ETL 的任务在同一个队列里抢资源谁提交的任务多谁占便宜。容量调度器的“容量保证”机制在单队列下完全失效因为根本没有第二个队列来体现“保证”和“借用”的关系。生产环境里队列结构通常要按业务线、部门或者任务优先级来划分。比如property nameyarn.scheduler.capacity.root.queues/name valueoffline,realtime,default/value /property这样就划出了离线、实时、默认三个队列。注意root.queues里列出的队列名必须和后面定义的root.队列名.capacity等参数对应上否则启动会报错。这是新手最容易踩的坑之一改了队列列表忘了加对应的容量配置。2.2 容量与最大容量100 和 -1 的组合意味着什么队列划好之后接下来最关键的是容量分配。默认配置里default 队列的容量是 100property nameyarn.scheduler.capacity.root.default.capacity/name value100/value /property property nameyarn.scheduler.capacity.root.default.maximum-capacity/name value-1/value /propertycapacity是队列的“保证容量”单位是百分比。所有同级队列的 capacity 加起来必须等于 100。default 队列独占 100意味着整个集群的资源都保证给它。maximum-capacity是队列能膨胀到的上限-1 表示“不限制”也就是可以涨到 100%。在单队列场景下这两个值其实是一回事因为总共就一个队列它本来就能用满。但当你划出多个队列后这两个参数的意义就出来了。假设三个队列 offline、realtime、default容量分别配 60、30、10property nameyarn.scheduler.capacity.root.offline.capacity/name value60/value /property property nameyarn.scheduler.capacity.root.realtime.capacity/name value30/value /property property nameyarn.scheduler.capacity.root.default.capacity/name value10/value /property这表示offline 队列保证能拿到 60% 的资源realtime 保证 30%default 保证 10%。如果 realtime 队列当前没任务它那 30% 的资源可以借给 offline 用offline 实际能用到 90%前提是它的 maximum-capacity 允许。这就是“容量保证 弹性借用”的核心机制。maximum-capacity默认是 -1但生产环境通常不会真的设成 -1。原因很简单如果 offline 队列的 maximum-capacity 不限制它可能把借来的资源一直占着等 realtime 队列突然来任务时虽然理论上 realtime 有 30% 的保证容量但实际资源已经被 offline 占用了realtime 的任务还是得等。所以更稳妥的做法是给每个队列设一个合理的 maximum-capacity比如 offline 设 70realtime 设 50default 设 20。这样既能借用又不会让某个队列无限膨胀。这里有个计算细节maximum-capacity 的值必须大于等于 capacity。如果 capacity 是 60maximum-capacity 设成 50配置校验会直接失败。另外所有队列的 capacity 之和必须严格等于 100差一点都不行这是硬性约束。2.3 资源分配策略默认的 FIFO 与 Fair 之争容量调度器内部每个队列还可以指定具体的资源分配策略。默认配置里这个参数是property nameyarn.scheduler.capacity.root.default.ordering-policy/name valuefifo/value /propertyfifo表示队列内部按先进先出分配资源。先提交的任务先拿资源后来的排队。这个策略简单、可预测但有个明显问题如果一个队列里既有跑几分钟的小任务又有跑几小时的大任务小任务会被大任务堵在后面体验很差。另一种策略是fair队列内部按公平原则分配多个任务可以同时拿到一部分资源而不是一个任务吃满。对于混合负载的队列fair 通常更合适。但默认给的是 fifo因为它的行为最容易理解调试起来也简单。实际生产中我一般会这样选离线批处理队列用 fifo因为批处理任务通常按提交顺序执行先来后到符合预期实时或交互式队列用 fair避免长任务阻塞短任务。这个选择没有绝对的对错关键看队列里跑的是什么类型的任务。2.4 资源限制单个任务能占多少默认配置里还有一组容易被忽略但很重要的参数控制单个任务能占用的资源上限property nameyarn.scheduler.capacity.root.default.maximum-am-resource-percent/name value0.1/value /property property nameyarn.scheduler.capacity.root.default.user-limit-factor/name value1/value /propertymaximum-am-resource-percent默认 0.1意思是 ApplicationMaster 最多能占用队列资源的 10%。这个参数的存在是为了防止大量小任务同时启动每个都占一个 AM 容器把资源耗尽导致真正的任务容器起不来。0.1 是个比较保守的值适合任务数量适中的场景。如果队列里经常有几百个小任务并发这个值可能需要调大比如 0.3 或 0.5否则 AM 容器不够用任务会一直卡在等待状态。user-limit-factor默认 1表示单个用户最多能使用队列 capacity 的 1 倍资源。注意这个限制是在队列 capacity 的基础上算的不是集群总资源。比如队列 capacity 是 30%user-limit-factor 是 1那单个用户最多用 30% 的集群资源。如果设成 2单个用户最多能用 60%但前提是队列的 maximum-capacity 允许。这个参数在多用户共享队列时特别有用能防止某个用户把整个队列的资源占满。2.5 其他默认参数那些“存在感低但别乱动”的配置除了上面几组核心参数默认配置里还有一些辅助参数比如property nameyarn.scheduler.capacity.node-locality-delay/name value40/value /property property nameyarn.scheduler.capacity.rack-locality-additional-delay/name value-1/value /propertynode-locality-delay默认 40表示调度器在放弃节点本地性之前会等待 40 次调度机会。这个参数影响的是数据本地化如果任务的数据在某个节点上调度器会优先把任务分配到那个节点。等待 40 次之后如果还没等到合适的节点就退而求其次分配到其他节点。40 这个值在大多数场景下够用但如果集群节点很多、任务很密集可能需要调大否则本地性还没满足就放弃了导致大量数据跨节点传输。rack-locality-additional-delay默认 -1表示不额外等待机架本地性。这个参数一般不用动除非你对机架感知有特殊要求。这些参数的特点是默认值在大多数场景下“能用”但一旦集群规模上去或者负载模式变化就需要针对性调整。我的建议是先把队列结构和容量分配搞清楚这些辅助参数等遇到具体问题时再调不要一上来就全改一遍。3. 生产环境怎么改从默认到可用的配置思路3.1 队列划分的三种常见模式默认的单队列结构在生产环境基本不可用但怎么划队列取决于你的业务形态。我见过的大多数集群队列划分逃不出下面三种模式。第一种是按业务线划分。比如一个公司有推荐、搜索、广告三条业务线那就划三个队列容量按业务重要性分配。这种模式的好处是隔离清晰每个业务线自己管自己的队列资源互不干扰。缺点是如果某个业务线暂时没任务它的资源虽然可以借给其他队列但借用的粒度是整个队列不够灵活。第二种是按任务类型划分。比如离线批处理、实时计算、交互式查询各一个队列。这种模式适合任务类型差异大的场景离线任务可以容忍排队实时任务需要低延迟交互式查询要求快速响应。给实时队列配较高的 capacity 和较低的 maximum-capacity保证它随时有资源可用给离线队列配较低的 capacity 但较高的 maximum-capacity让它能在空闲时充分利用资源。第三种是混合模式先按业务线分一级队列再在每个业务线内部按任务类型分二级队列。比如property nameyarn.scheduler.capacity.root.queues/name valuebusinessA,businessB/value /property property nameyarn.scheduler.capacity.root.businessA.queues/name valueoffline,realtime/value /property这种结构最灵活但配置也最复杂。队列层级越深资源分配的计算越绕出问题时排查也越麻烦。我的经验是队列层级不要超过三层同级队列数量不要超过十个。超过这个规模配置维护成本会急剧上升而且很容易出现某个队列容量配错导致整体校验失败的情况。3.2 容量分配的计算过程与实例假设一个集群有 100 个容器资源只是为了方便计算实际是内存和 CPU 的组合我们划了三个一级队列offline、realtime、default。业务方的要求是离线任务保证 50%实时任务保证 30%剩余 20% 作为兜底。第一步确定 capacityproperty nameyarn.scheduler.capacity.root.offline.capacity/name value50/value /property property nameyarn.scheduler.capacity.root.realtime.capacity/name value30/value /property property nameyarn.scheduler.capacity.root.default.capacity/name value20/value /property50 30 20 100校验通过。第二步确定 maximum-capacity。离线队列可以在实时队列空闲时借用资源但最多不超过 80%给实时队列留出至少 20% 的缓冲property nameyarn.scheduler.capacity.root.offline.maximum-capacity/name value80/value /property property nameyarn.scheduler.capacity.root.realtime.maximum-capacity/name value50/value /property property nameyarn.scheduler.capacity.root.default.maximum-capacity/name value30/value /property这里有个细节maximum-capacity 不需要加起来等于 100它只是每个队列自己的上限。但所有队列的 maximum-capacity 之和如果小于 100意味着集群资源永远用不满如果大于 100意味着理论上可以超卖但实际受限于物理资源。通常让它们的和略大于 100 是合理的这样在负载不均时能充分利用资源。第三步设置 user-limit-factor。假设 offline 队列有多个用户提交任务为了防止单个用户占满整个队列设成 1property nameyarn.scheduler.capacity.root.offline.user-limit-factor/name value1/value /property这样单个用户最多用 offline 队列 capacity 的 1 倍也就是集群的 50%。如果希望更宽松可以设成 1.5 或 2但要配合 maximum-capacity 一起考虑。3.3 动态调整与热加载的注意事项改完capacity-scheduler.xml后需要让 YARN 重新加载配置。默认情况下容量调度器支持动态更新不需要重启 ResourceManager。操作方式是yarn rmadmin -refreshQueues这个命令会让 ResourceManager 重新读取配置文件并更新队列结构。但有几个坑必须注意。第一动态更新只能修改队列的容量、maximum-capacity、user-limit-factor 等参数不能增加或删除队列。如果要新增队列必须重启 ResourceManager。这一点很多人不知道改了root.queues之后执行 refreshQueues发现没生效还以为命令有问题。第二refreshQueues 不会影响正在运行的任务。已经分配出去的容器继续运行新配置只对后续的调度生效。所以调整容量后不要指望立刻看到资源重新分配需要等当前任务陆续结束。第三如果配置有误refreshQueues 会报错但 ResourceManager 会继续使用旧配置。这时候要去看 ResourceManager 的日志找到具体的校验失败原因。常见的错误包括capacity 之和不为 100、maximum-capacity 小于 capacity、队列名拼写不一致等。提示每次修改 capacity-scheduler.xml 之前先备份一份。动态刷新失败时旧配置还在运行但如果你手动改错了文件又没备份重启后就起不来了。4. 常见问题与排查技巧实录4.1 任务一直卡在 ACCEPTED 状态这是最常被问到的问题之一。任务提交后一直处于 ACCEPTED不进 RUNNING通常有几个原因。第一个原因是队列资源被占满且没有空闲资源可以借用。检查方法是看队列的实际使用率和 capacity。如果队列使用率已经达到 maximum-capacity新任务只能排队。这时候要么等现有任务释放资源要么调整 maximum-capacity。第二个原因是 AM 容器不够。前面提到的maximum-am-resource-percent如果设得太小比如 0.1而队列里同时提交了大量任务AM 容器很快用完后续任务就无法启动。排查方法是看 ResourceManager 日志里有没有 “Waiting for AM container to be allocated” 之类的信息。解决办法是适当调大这个值比如 0.3。第三个原因是 user-limit-factor 限制。如果单个用户提交的任务太多达到了 user-limit-factor 的上限新任务也会排队。这时候可以临时调大 user-limit-factor或者让用户分散到不同队列提交。4.2 队列容量之和不等于 100 导致启动失败这是配置阶段最常见的错误。容量调度器要求同一层级下所有队列的 capacity 之和必须精确等于 100。注意是“精确”99.9 和 100.1 都不行。出现这个问题的原因通常是手动计算时四舍五入导致的。比如三个队列按 1/3 分配每个写 33.33加起来是 99.99校验失败。解决办法是手动调整让其中一个队列承担余数比如 33.34、33.33、33.33。另一个原因是新增队列后忘了调整原有队列的容量。比如原来两个队列各 50现在加一个队列新队列配了 20但原来的两个还是 50加起来 120直接报错。每次改队列结构都要重新算一遍总和。4.3 资源借用不生效的排查思路容量调度器的弹性借用机制有时候看起来“没起作用”。比如 offline 队列空闲realtime 队列任务排队但 realtime 就是拿不到 offline 的资源。这种情况通常有几个排查点。先看 offline 队列的 maximum-capacity 是不是设得太低。如果 offline 的 maximum-capacity 是 50而它的 capacity 也是 50那它根本没有膨胀空间自然借不出资源。借用是双向的空闲队列的资源要能被其他队列“借走”前提是其他队列的 maximum-capacity 允许它们膨胀。再看 realtime 队列的 maximum-capacity。如果 realtime 的 maximum-capacity 等于 capacity比如都是 30那它最多只能用 30%即使 offline 空闲 50%realtime 也用不了。必须把 realtime 的 maximum-capacity 设得比 capacity 大比如 60它才有资格借用。最后看调度器的资源计算周期。容量调度器不是实时重新计算资源分配的它有一个调度间隔。资源借用的生效可能有几十秒到几分钟的延迟取决于集群规模和任务数量。如果刚调整完配置就观察可能还没生效。4.4 常见问题速查表问题现象可能原因排查方法解决思路任务卡在 ACCEPTED队列资源满、AM 容器不足、用户限制看队列使用率、RM 日志、user-limit-factor调大 maximum-capacity 或 AM 资源比例启动报容量校验失败capacity 之和不为 100检查所有同级队列的 capacity 值手动调整使总和精确等于 100资源借用不生效maximum-capacity 设置不当对比 capacity 和 maximum-capacity确保借用方 maximum-capacity 大于 capacityrefreshQueues 不生效试图动态增删队列检查是否修改了队列列表增删队列需重启 ResourceManager单用户占满队列user-limit-factor 过大查看该用户的任务数量和资源占用调小 user-limit-factor 或分散提交4.5 几个我踩过的坑第一个坑以为capacity是“最多能用多少”。实际上 capacity 是“保证能用多少”不是上限。上限是 maximum-capacity。这个理解偏差会导致容量分配完全算错。我刚开始接触时把 capacity 当成上限来配结果队列之间完全没法借用资源集群利用率一直上不去。第二个坑忽略了user-limit-factor的默认值。默认是 1看起来很小但在单队列场景下1 意味着单个用户可以用满整个队列。如果队列 capacity 是 100那单个用户就能用 100% 的集群资源。多用户环境下这会导致严重的资源争抢。后来我把这个值调成 0.5配合多队列结构才解决了问题。第三个坑动态刷新后没观察实际效果。执行refreshQueues后看到命令返回成功就以为配置生效了。实际上正在运行的任务不会重新分配资源新配置只影响后续调度。有一次我调整了队列容量发现资源分布没变化以为配置没生效反复刷新了好几次后来才意识到是运行中的任务还没结束。第四个坑队列命名不一致。root.queues里写的是offline但容量配置里写的是root.offline.capacity看起来没问题。但如果root.queues里写的是offline,realtime而容量配置里漏了root.realtime.capacity启动时就会报错。更隐蔽的情况是大小写不一致比如Offline和offline在某些版本里会导致队列识别失败。5. 从默认配置到生产配置的完整参考5.1 一份可直接参考的多队列配置模板下面这份配置是我在一个中等规模集群上实际用过的三个队列容量 50/30/20支持弹性借用单用户限制在队列容量的 1 倍。你可以根据自己的集群规模调整数值。configuration !-- 根队列下的子队列列表 -- property nameyarn.scheduler.capacity.root.queues/name valueoffline,realtime,default/value /property !-- offline 队列保证 50%最多膨胀到 80% -- property nameyarn.scheduler.capacity.root.offline.capacity/name value50/value /property property nameyarn.scheduler.capacity.root.offline.maximum-capacity/name value80/value /property property nameyarn.scheduler.capacity.root.offline.user-limit-factor/name value1/value /property property nameyarn.scheduler.capacity.root.offline.ordering-policy/name valuefifo/value /property !-- realtime 队列保证 30%最多膨胀到 60% -- property nameyarn.scheduler.capacity.root.realtime.capacity/name value30/value /property property nameyarn.scheduler.capacity.root.realtime.maximum-capacity/name value60/value /property property nameyarn.scheduler.capacity.root.realtime.user-limit-factor/name value1/value /property property nameyarn.scheduler.capacity.root.realtime.ordering-policy/name valuefair/value /property !-- default 队列保证 20%最多膨胀到 40% -- property nameyarn.scheduler.capacity.root.default.capacity/name value20/value /property property nameyarn.scheduler.capacity.root.default.maximum-capacity/name value40/value /property property nameyarn.scheduler.capacity.root.default.user-limit-factor/name value1/value /property property nameyarn.scheduler.capacity.root.default.ordering-policy/name valuefifo/value /property !-- 全局 AM 资源比例按需调整 -- property nameyarn.scheduler.capacity.maximum-am-resource-percent/name value0.2/value /property /configuration这份配置的几个设计考虑offline 队列用 fifo因为批处理任务按提交顺序执行符合预期realtime 队列用 fair避免长任务阻塞短任务default 队列作为兜底容量最小但也能借用。maximum-capacity 的总和是 80 60 40 180远大于 100这意味着在负载不均时资源可以充分流动。5.2 配置生效后的验证步骤改完配置并执行refreshQueues后不要只看命令返回成功就完事。建议按下面的步骤验证一遍。第一步用yarn queue -status 队列名查看队列状态。这个命令会显示队列的当前容量、已用容量、最大容量等信息。确认 capacity 和 maximum-capacity 和你配置的一致。第二步提交一个测试任务到指定队列观察它是否能正常拿到资源。可以用hadoop jar提交一个简单的 sleep 任务指定-D mapreduce.job.queuenameoffline看任务是否进入 RUNNING 状态。第三步模拟资源借用场景。往 offline 队列提交一个占满 50% 资源的任务再往 realtime 队列提交任务观察 realtime 是否能借用 offline 的空闲资源。如果 realtime 的任务能正常启动说明借用机制生效了。第四步检查 ResourceManager 日志搜索 “Capacity Scheduler” 相关的信息看有没有队列初始化、容量校验、资源分配的记录。日志里通常会打印每个队列的配置摘要对照一下和你预期的是否一致。5.3 监控与持续调优的建议配置不是一次性的工作。集群的负载模式会变业务需求会变队列容量也需要跟着调。建议把下面几个指标纳入日常监控。队列使用率每个队列的实际资源使用量占集群总资源的比例。如果某个队列长期接近 maximum-capacity说明它需要更多资源或者它的任务需要优化。队列等待任务数处于 ACCEPTED 状态的任务数量。如果某个队列持续有大量任务排队而其他队列空闲说明容量分配不合理或者借用机制没生效。AM 容器使用率当前活跃的 ApplicationMaster 数量占最大允许数量的比例。如果长期接近 100%说明maximum-am-resource-percent需要调大。单用户资源占比单个用户在队列中的资源使用比例。如果经常有用户触及 user-limit-factor 上限说明这个值需要调整或者需要引导用户分散到不同队列。这些指标可以通过 ResourceManager 的 Web UI 或者 REST API 获取也可以接入监控系统做长期趋势分析。调优是个迭代过程不要指望一次配置就永久适用。5.4 关于默认配置的最后几句回到最初的问题capacity-scheduler.xml的默认配置到底能不能用答案是能跑但只能跑最最简单的场景。一旦集群有多用户、多任务类型、资源争抢的情况默认配置就会暴露出一堆问题。理解默认配置的价值不在于直接使用它而在于理解每个参数的默认值背后的设计意图。知道了为什么默认是单队列、为什么 capacity 是 100、为什么 maximum-capacity 是 -1你才能判断在什么场景下需要改、改成什么值、改了之后会有什么影响。我个人在实际操作中的体会是队列配置这件事宁可一开始多花半小时想清楚业务形态和资源分配也不要等集群跑起来后频繁调整。频繁 refreshQueues 虽然不用重启但每次调整都会影响正在排队的任务对业务方来说体验并不好。把队列结构设计得稍微超前一点给未来留出扩展空间比事后打补丁要省心得多。