
1. 从单队列踩坑说起为什么一定要做多队列配置刚接触 YARN 那会儿我图省事整个集群就挂了一个 default 队列所有任务不分类型全往里塞。小打小闹的时候没感觉直到有一次离线跑批任务和实时查询任务撞到一起——跑批把资源吃干抹净实时查询排了十几分钟队才拿到容器业务方电话直接打到我这里。那次之后我才真正意识到YARN 的容量调度器Capacity Scheduler如果不做多队列规划本质上就是把整个集群的稳定性交给运气。容量调度器的核心设计目标就是让多个组织、多个业务线共享一个大集群同时保证每个队列都有保底资源和上限天花板。它和公平调度器Fair Scheduler最大的区别在于容量调度器强调队列级别的资源保障每个队列预先分配一个容量百分比即使集群空闲其他队列也不能无限制抢占而公平调度器更强调任务级别的动态均衡。对于有明确业务划分、需要做资源隔离的团队来说容量调度器几乎是默认选择。这篇文章要聊的就是容量调度器下多队列到底怎么配、每个参数背后是什么逻辑、配错了会出现什么现象。我会从整体设计思路讲到具体配置文件再到实际验证和排错尽量把我在生产环境里踩过的坑都摊开说。适合正在做集群资源规划、被队列抢占问题困扰、或者准备从单队列迁移到多队列的运维和开发同学。哪怕你之前只改过capacity-scheduler.xml里的一两个值看完应该也能把整套多队列体系搭起来。2. 多队列整体设计与容量模型拆解2.1 容量调度器的三层资源模型理解多队列配置先要理解容量调度器的资源分配是分层的。它把整个集群资源按树形结构切分大致是这么三层根队列root整个集群资源的入口所有子队列的容量之和不能超过 100%。一级子队列通常按业务线或部门划分比如offline离线、realtime实时、etl数据加工。叶子队列真正提交任务的地方可以继续嵌套子队列但一般两层就够了。每一层都有自己的capacity保底容量和maximum-capacity弹性上限。这里有个关键点很多人一开始会搞混capacity 是保底不是独占。意思是当队列 A 的资源没被用满时队列 B 可以借用这部分空闲资源但一旦 A 有任务进来B 需要把借用的资源还回去。而maximum-capacity才是队列真正能膨胀到的天花板。举个具体例子。假设集群总资源 100 个虚拟核配置如下队列capacitymaximum-capacityoffline60%80%realtime30%50%etl10%30%当三个队列都满负荷时offline 至少能拿到 60 核realtime 至少 30 核etl 至少 10 核。如果 realtime 和 etl 都很闲offline 最多能涨到 80 核。这就是保底 弹性的组合拳。2.2 为什么用百分比而不是绝对值配置里用的是百分比不是具体的核数或内存数。这个设计很聪明——集群扩容时你不需要改队列配置所有队列按比例自动放大。比如上面 offline 是 60%集群从 100 核扩到 200 核offline 的保底自动变成 120 核。但百分比也有个坑所有同级队列的 capacity 之和必须等于 100%多一个百分点少一个百分点都会导致调度器启动失败。我见过有人配成 60 30 20 110结果 ResourceManager 直接起不来日志里报sum of capacities of children queues is more than 100。这个错误排查起来其实不难但第一次遇到容易懵。2.3 队列层级设计的两条经验法则关于队列怎么划分我总结了两条实战经验第一按资源使用特征分而不是按团队分。很多团队一开始按部门建队列结果发现 A 部门和 B 部门的任务特征几乎一样都是白天跑 SQL、晚上跑批分队列反而增加了管理成本。更好的做法是按任务类型分短任务、长任务、高内存任务、高 CPU 任务这样资源画像清晰容量规划也准。第二队列层级不要超过三层。层级越深资源借用的链路越长调度开销越大而且排查问题时要在多层之间来回跳。两层业务线 叶子队列能覆盖 90% 的场景。3. 核心配置参数逐条拆解与实操要点3.1 capacity-scheduler.xml 的骨架结构容量调度器的所有配置都在capacity-scheduler.xml里核心是yarn.scheduler.capacity.root.这个前缀下的一系列属性。下面是一个最小可用的多队列配置骨架configuration property nameyarn.scheduler.capacity.root.queues/name valueoffline,realtime,etl/value /property property nameyarn.scheduler.capacity.root.offline.capacity/name value60/value /property property nameyarn.scheduler.capacity.root.offline.maximum-capacity/name value80/value /property property nameyarn.scheduler.capacity.root.realtime.capacity/name value30/value /property property nameyarn.scheduler.capacity.root.realtime.maximum-capacity/name value50/value /property property nameyarn.scheduler.capacity.root.etl.capacity/name value10/value /property property nameyarn.scheduler.capacity.root.etl.maximum-capacity/name value30/value /property /configuration注意root.queues这一行它定义了根队列下有哪些子队列顺序无所谓但名字必须和后面配置的属性名严格对应。我踩过一次坑queues里写的是offline但下面配置写成了root.offLine.capacity大小写不一致结果调度器认为 offline 队列没有配置容量直接报错。队列名大小写敏感务必逐字核对。3.2 用户与应用的资源限制参数光配队列容量还不够还得控制单个用户和单个应用能占多少资源否则一个用户提交一堆任务就能把整个队列吃满。相关参数有这么几个参数作用建议值minimum-user-limit-percent单用户最低保障资源百分比通常 10~25user-limit-factor单用户可用的队列容量倍数1~2maximum-am-resource-percentApplicationMaster 可占队列资源上限0.1~0.2minimum-user-limit-percent这个参数特别容易被忽略。假设队列有 100 核这个值设成 25那么当有 4 个以上用户同时提交任务时每个用户至少能拿到 25 核。如果设成 100那就等于没有用户隔离先到的用户能把队列占满。生产环境我一般设 10 到 25 之间看队列里活跃用户数量决定。user-limit-factor控制的是单用户能突破保底容量多少倍。设成 1 表示用户最多用到队列的 capacity设成 2 表示可以借用到 2 倍。这个值设太大用户之间会互相挤压设太小单用户跑大任务时又不够用。我的经验是默认 1对个别需要跑大任务的队列单独调高。3.3 资源抢占与队列状态控制容量调度器支持资源抢占preemption这是保证保底容量真正生效的关键机制。相关配置property nameyarn.scheduler.capacity.root.offline.disable_preemption/name valuefalse/value /propertydisable_preemption默认是 false也就是允许抢占。当 offline 队列资源被 realtime 借走而 offline 突然来了任务调度器会从 realtime 那里抢回资源。抢占的粒度是容器container被抢占的任务会被 kill 然后重新调度。注意抢占会 kill 正在运行的任务对长任务非常不友好。如果你的离线队列跑的是几小时的大批处理建议对这类队列谨慎开启抢占或者配合yarn.scheduler.capacity.preemption.max_ignored_over_capacity等参数控制抢占的激进程度。另外还有队列状态控制yarn.scheduler.capacity.root.offline.state可以设成RUNNING或STOPPING。设成 STOPPING 后队列不再接受新任务但已有任务继续跑完。这个在队列下线或迁移时特别有用能做到平滑过渡。4. 完整实操流程从配置到验证4.1 配置修改与生效方式改完capacity-scheduler.xml后有两种生效方式重启 ResourceManager最彻底但会中断所有正在运行的任务调度生产环境慎用。动态刷新执行yarn rmadmin -refreshQueues不重启就能加载新配置。动态刷新是首选但有个限制只能修改容量、上限、状态等参数不能新增或删除队列。也就是说如果你要加一个新队列还是得重启 RM。我一般先在测试环境验证新增队列的配置确认无误后再上生产重启。刷新命令执行后可以通过 RM 的 Web UI默认 8088 端口查看队列树确认新配置是否生效。UI 上能看到每个队列的容量、已用资源、运行中的应用数非常直观。4.2 提交任务验证队列隔离配置生效后必须实际提交任务验证。我通常用hadoop jar跑一个简单的 sleep 任务指定队列hadoop jar /path/to/hadoop-mapreduce-examples.jar sleep \ -D mapreduce.job.queuenameoffline \ -m 10 -r 1 -mt 60000 -rt 60000这个命令会启动 10 个 map 任务每个跑 60 秒。同时往 realtime 队列也提交一批任务观察两边资源占用是否符合预期。重点看两个指标offline 队列在 realtime 空闲时能否涨到 maximum-capacity80%。realtime 队列有任务进来时offline 是否被压缩回 capacity60%。如果 offline 一直卡在 60% 上不去说明maximum-capacity没生效检查配置里是不是漏了或者写错了。如果 realtime 进来后 offline 没被压缩说明抢占没生效检查disable_preemption和抢占相关参数。4.3 用调度器日志定位资源分配过程光看 UI 有时候不够还得看调度器日志。RM 的日志里会打印每次资源分配和抢占的决策过程关键字是CapacityScheduler和preempt。比如grep -i preempt $HADOOP_HOME/logs/hadoop-*-resourcemanager-*.log | tail -50日志里能看到类似Preempting container xxx from queue realtime to queue offline的记录说明抢占确实发生了。如果日志里完全没有抢占记录而 offline 又确实缺资源那就要检查是不是disable_preemption被设成了 true或者队列的maximum-capacity设得太低导致没有可抢占的空间。4.4 队列资源计算的实际案例假设集群有 3 台 NodeManager每台配置 32 核、128GB 内存但 YARN 实际可用的是每台 28 核、120GB留一部分给系统。那么集群总资源是 84 核、360GB。按前面的配置offline 的保底是 84 × 60% 50.4 核取整 50 核内存 360 × 60% 216GB。maximum-capacity 80% 对应 67 核、288GB。这些数字在规划时要心里有数因为队列容量是按资源算的而资源包含 CPU 和内存两个维度调度器取两者中更紧张的那个作为约束。我遇到过一种情况CPU 还剩很多但内存已经到队列上限了任务照样提交不进去。所以规划队列时CPU 和内存的配比要一起考虑不能只看核数。5. 常见问题与排查技巧实录5.1 队列配置不生效的几种典型原因配置改完不生效是最常见的问题。我整理了一张速查表现象可能原因排查方法刷新后队列没变化没执行 refreshQueues重新执行yarn rmadmin -refreshQueues新增队列不出现动态刷新不支持新增队列重启 ResourceManager容量之和不等于 100配置错误检查同级队列 capacity 之和队列名大小写不一致属性名拼写错误逐字核对 queues 列表和属性名任务提交报队列不存在队列未在 queues 列表声明检查父队列的 queues 属性其中容量之和不等于 100这个错误RM 启动时会直接失败日志里会明确提示。但如果是动态刷新时出错RM 不会崩溃只是新配置不生效日志里会有 WARN。所以刷新后一定要看日志确认没有报错别以为命令返回 0 就万事大吉。5.2 资源被单一用户或应用占满队列配好了但某个用户提交了几百个任务把整个队列占满其他用户提交不进去。这是用户限制参数没配好。解决思路调低minimum-user-limit-percent比如从 100 调到 20。设置user-limit-factor为 1限制单用户不能借用超过队列容量的资源。对个别大用户可以单独建子队列把它隔离出去。还有一种情况是单个应用申请了超大容器比如一个任务要 64GB 内存直接把队列内存吃光。这时候要检查maximum-am-resource-percent确保 AM 不会占用过多资源同时和业务方沟通把大任务拆小。5.3 抢占导致任务频繁失败开启抢占后如果配置过于激进会出现任务被反复 kill 的情况。日志里能看到大量Container preempted记录。这时候要调整抢占参数yarn.scheduler.capacity.preemption.max_ignored_over_capacity控制队列超出容量多少才触发抢占调大可以降低抢占频率。yarn.scheduler.capacity.preemption.natural_termination_factor控制被抢占任务的自然结束比例调大可以让任务有机会自己跑完。我的经验是对跑批队列尽量不开启抢占或者把抢占阈值调得很宽松。跑批任务动辄几小时被 kill 一次重跑成本太高。实时队列可以激进一点因为任务短重跑代价小。5.4 队列资源利用率的监控要点多队列上线后监控必须跟上。我重点关注三个指标各队列实际资源使用率长期低于 capacity 说明容量分配过多可以回收。队列等待任务数持续大于 0 说明容量不足需要扩容或调整。抢占次数频繁抢占说明容量规划不合理队列之间争抢严重。这些指标在 RM Web UI 上都能看到也可以接入监控系统做告警。我一般设置两条告警线队列使用率连续 30 分钟超过 90%以及队列等待任务数超过 50。6. 多队列配置的进阶玩法与个人体会6.1 基于标签的队列资源隔离容量调度器还支持节点标签Node Label可以把特定节点打上标签只分配给特定队列。比如把几台高内存机器打上highmem标签只给需要大内存的队列用。这个功能在资源异构的集群里特别有用能做到物理级别的隔离。配置方式是在yarn-site.xml里开启标签调度然后用yarn rmadmin命令给节点打标签最后在队列配置里指定accessible-node-labels。这块内容比较多展开能单独写一篇这里先提一句知道有这条路就行。6.2 队列配置的版本管理与回滚生产环境的队列配置一定要做版本管理。我习惯把capacity-scheduler.xml放进 Git每次修改都提交commit message 写清楚改了什么、为什么改。这样出问题时能快速对比和回滚。回滚的时候注意如果新配置新增了队列回滚到旧配置需要重启 RM如果只是改了容量数值动态刷新即可。所以修改前先想清楚这次改动是否涉及队列增删涉及的话要安排重启窗口。6.3 我踩过的三个真实坑第一个坑是容量之和算错。有一次我把三个队列配成 50 30 30想着加起来 110 也没事结果 RM 直接起不来。后来才知道同级队列之和必须严格等于 100多一点都不行。第二个坑是忘了配 maximum-capacity。只配了 capacity结果队列永远只能用保底容量空闲资源全浪费了。maximum-capacity 不配的话默认等于 capacity等于没有弹性。第三个坑是动态刷新后没验证。有一次刷新命令返回成功我以为生效了结果第二天发现新队列根本没起来。后来看日志才发现刷新时有个队列名拼错了RM 报了 WARN 但没中断刷新其他队列正常更新就那个错的没生效。从那以后我每次刷新后都会去 Web UI 上确认一遍队列树。6.4 容量规划的一个实用公式最后分享一个我常用的容量规划公式帮你在配置前估算队列大小队列保底资源 集群总资源 × capacity% 队列弹性资源 集群总资源 × maximum-capacity% 队列实际可用 min(队列弹性资源, 集群空闲资源 队列保底资源)这个公式的关键在于最后一行队列实际能用的资源取决于集群当时有多少空闲资源。所以maximum-capacity 设得再高集群没空闲资源也是白搭。规划时要把所有队列的 maximum-capacity 之和考虑进去如果总和远超 100%说明大家都在抢实际谁都拿不到上限。我个人在实际操作中的体会是多队列配置没有一劳永逸的方案业务在变资源需求也在变。我一般每季度 review 一次队列配置看看哪些队列长期闲置、哪些长期排队然后做相应调整。配置本身不难难的是持续观察和迭代。把监控做好让数据说话比拍脑袋定容量靠谱得多。