1. 从一个真实困境说起为什么单队列迟早要出问题刚接触YARN的时候绝大多数人都是默认配置一路走到底。装完集群yarn-site.xml里配个ResourceManager地址启动提交任务能跑就行。这个阶段用默认调度器、默认队列确实不会出什么大问题——因为就你一个人在用所有资源都是你的。但集群一旦进入多人共用阶段事情就变了。我见过太多团队在这个节点上翻车数据组跑一个大型批处理把整个集群的CPU和内存吃到95%以上一跑就是四五个小时与此同时算法组想提交一个小的模型训练任务验证想法结果在队列里排了半小时都拿不到资源。算法组的人去找运维运维说资源被数据组占满了等吧。数据组的人也很委屈我们的任务本来就重又不是故意的。这个场景的本质问题是所有任务共享同一个资源池没有隔离没有优先级没有配额约束。YARN默认的Capacity Scheduler虽然叫“容量调度器”但如果你不配置多队列它就只有一个default队列容量100%最大容量100%。这意味着任何一个任务都可以吃掉整个集群的资源先到先得后来者干等。所以多队列配置不是“高级功能”而是集群从单人玩具走向多人生产环境的必经之路。它的核心价值就三件事资源隔离你的任务不会把我的资源吃光、配额保障我至少能拿到我应得的那部分、弹性共享你闲着的时候我可以借用你的资源。这三件事听起来简单但真正配起来里面有不少门道。这篇文章面向的是已经跑通了YARN基础集群、准备做多队列资源管理的开发和运维人员。我会从队列树的设计逻辑讲起把Capacity Scheduler的核心配置参数一个个拆开解释每个参数背后的计算逻辑和实际影响然后给出可以直接参考的配置模板最后分享几个我在实际配置中踩过的坑和排查思路。读完之后你应该能独立设计一套适合自己团队规模的多队列方案并且知道每个参数改了之后会发生什么。2. 队列树的设计逻辑不是随便画几个框就行2.1 队列层级到底应该怎么划分很多人第一次配多队列脑子里想的是“按部门分”——数据部一个队列算法部一个队列工程部一个队列。这个思路没错但不够。队列的划分维度其实有三种常见模式选哪种取决于你的组织结构和任务特征。按团队/部门划分是最直观的。每个部门一个队列部门内部的资源分配由部门自己管理。这种模式适合组织结构清晰、各部门任务类型相对独立的场景。好处是责任明确每个部门知道自己有多少资源不会互相甩锅。坏处是如果某个部门任务量波动大容易出现“旱的旱死、涝的涝死”。按任务类型划分是另一种思路。比如批处理任务一个队列、交互式查询一个队列、流式任务一个队列。这种划分的核心逻辑是不同任务对资源的需求特征不同批处理任务通常跑得久、占资源多、对延迟不敏感交互式查询要求快速响应、占资源少但频繁流式任务则是长期驻留、资源需求稳定。把它们混在一起调度器很难做优化。分开之后你可以给交互式队列配更高的优先级和更小的资源粒度给批处理队列配更大的容量但允许它慢慢跑。按优先级划分是最简单粗暴的高优先级队列、中优先级队列、低优先级队列。这种模式适合任务重要性差异明显、但团队边界模糊的场景。比如线上紧急修复任务放高优先级日常开发任务放中优先级离线报表任务放低优先级。实际生产中这三种模式往往是组合使用的。我见过一个比较合理的结构是这样的顶层按部门分部门下面再按任务类型分。比如root ├── data_team │ ├── batch (批处理) │ └── adhoc (即席查询) ├── algo_team │ ├── training (模型训练) │ └── inference (推理服务) └── default这个结构的好处是部门之间有资源隔离部门内部又可以根据任务特征做差异化配置。data_team的批处理任务再重也不会影响algo_team的训练任务而data_team内部的即席查询任务因为配了更高的优先级可以在批处理任务占满资源时仍然抢到一部分资源快速执行。2.2 叶子队列和父队列的关系理解队列树的一个关键点是只有叶子队列才能提交任务父队列只负责资源分配。也就是说上面的例子里你不能直接往data_team提交任务只能往data_team.batch或data_team.adhoc提交。父队列的容量是它下面所有子队列容量的总和。比如data_team配了60%的容量那batch和adhoc的容量加起来不能超过60%。这个约束在配置的时候YARN会检查如果子队列容量之和超过父队列启动会报错。这里有一个容易混淆的概念容量capacity和最大容量maximum-capacity的区别。容量是“保障资源”最大容量是“上限资源”。举个例子data_team的容量配60%最大容量配80%意思是正常情况下data_team至少能拿到60%的集群资源如果集群有空闲资源其他队列没用满data_team最多可以扩展到80%但无论如何不会超过80%。这个机制就是Capacity Scheduler“弹性共享”的核心。它保证了每个队列的底线同时允许队列在资源空闲时突破底线提高整体利用率。2.3 队列命名和提交时的对应关系队列的命名在配置文件中是用.分隔的层级结构。比如data_team.batch表示data_team下面的batch子队列。提交任务的时候通过-D mapreduce.job.queuenamedata_team.batch来指定队列。如果不指定任务会进入default队列。这里有个实操细节队列名是大小写敏感的。我见过有人配置里写的是data_team提交时写的是Data_Team结果任务一直进不去指定队列排查了半天才发现是大小写问题。建议统一用小写加下划线的命名风格避免这种低级错误。另外队列的层级不要太深。我建议最多三层根队列下面一层部门部门下面一层任务类型。再深的话配置复杂度急剧上升而且资源分配的粒度会变得很细容易出现某个叶子队列资源很少、任务排队严重的情况。两层其实对大多数团队已经够用了。3. Capacity Scheduler核心参数拆解每个数字背后都是权衡3.1 capacity与maximum-capacity的计算逻辑这两个参数是多队列配置的基石但很多人配的时候是拍脑袋填的。我来解释一下正确的计算思路。假设集群总资源是100个虚拟核vcore和400GB内存。你有三个部门数据部、算法部、工程部。根据业务重要性你决定资源分配比例是5:3:2。那么data_team的capacity 50%对应50 vcore、200GB内存algo_team的capacity 30%对应30 vcore、120GB内存eng_team的capacity 20%对应20 vcore、80GB内存这三个加起来正好100%这是必须的——所有顶层队列的capacity之和必须等于100%。如果加起来不到100%剩下的部分会自动分配给default队列如果超过100%配置会报错。maximum-capacity的设置需要更谨慎。它决定了队列能弹性扩展的上限。如果设成100%意味着这个队列理论上可以吃掉整个集群的资源当其他队列空闲时。这听起来很美好但有个隐患如果data_team的任务突然暴增把资源扩展到100%这时候algo_team提交了新任务虽然algo_team有30%的保障容量但data_team已经占用的资源不会立刻释放——YARN只能等data_team的任务自然结束或者被抢占如果开启了抢占。所以maximum-capacity设得太高会削弱资源隔离的效果。我的经验值是maximum-capacity设为capacity的1.5到2倍之间。比如data_teamcapacity是50%maximum-capacity可以设75%到100%。具体设多少取决于你对“弹性”和“隔离”的权衡。如果业务对隔离要求高就设低一点如果追求集群利用率就设高一点。还有一个细节maximum-capacity必须大于等于capacity。这个不用多说但配置的时候容易写反。3.2 资源维度内存和CPU的独立配置Capacity Scheduler支持按内存和CPU两个维度分别配置容量。在capacity-scheduler.xml里你需要同时配yarn.scheduler.capacity.root.data_team.capacity内存百分比和yarn.scheduler.capacity.root.data_team.maximum-capacity以及对应的CPU配置。但这里有个坑默认情况下CPU资源的调度是关闭的。你需要设置yarn.scheduler.capacity.resource-calculator为org.apache.hadoop.yarn.util.resource.DominantResourceCalculator才能让CPU资源参与调度计算。如果用的是默认的DefaultResourceCalculatorYARN只会看内存CPU配了也不生效。这个坑我踩过。当时配了CPU的capacity但发现任务调度完全不按CPU来内存大的任务照样把队列占满。后来查了文档才发现是resource-calculator的问题。改成DominantResourceCalculator之后调度器会同时考虑内存和CPU取两者中占比更高的那个作为主导资源。比如一个任务需要10%的内存和20%的CPU那它的主导资源就是CPU调度时按20%来算。注意切换resource-calculator会影响已有任务的调度行为建议在集群空闲时操作或者在新集群部署时就配好。3.3 用户和应用的资源限制除了队列级别的容量控制Capacity Scheduler还提供了更细粒度的限制yarn.scheduler.capacity.root.data_team.user-limit-factor单个用户最多可以使用队列容量的多少倍。默认是1意思是单个用户最多用满队列的capacity。如果设成2单个用户可以用到队列capacity的2倍但不能超过maximum-capacity。这个参数用来防止队列内某个用户独占资源。yarn.scheduler.capacity.root.data_team.maximum-am-resource-percentApplicationMaster最多占用队列资源的百分比。默认是10%。这个参数很关键——如果设得太高大量AM会占用过多资源导致实际运行任务的资源减少如果设得太低并发任务数会受限。对于批处理队列可以设低一点比如5%到10%对于交互式队列因为任务数多但每个任务小可以适当调高。yarn.scheduler.capacity.root.data_team.minimum-user-limit-percent队列内单个用户的最低资源保障百分比。默认是100意思是如果队列里只有一个用户他可以用全部资源如果有两个用户每人至少50%以此类推。这个参数保证了多用户场景下的公平性。这些参数不是孤立的它们之间有联动关系。比如user-limit-factor和minimum-user-limit-percent都会影响单个用户能拿到的资源上限实际生效的是两者中更严格的那个约束。3.4 队列状态与动态刷新配置改完之后不需要重启YARN就能生效。执行yarn rmadmin -refreshQueues命令ResourceManager会重新加载队列配置。这个命令是热更新的不会影响正在运行的任务。但有几个注意事项第一不能通过refreshQueues删除正在使用的队列。如果某个队列里还有任务在跑你把它从配置里删掉刷新会报错。需要等任务跑完或者先kill掉任务。第二不能修改队列的层级结构。比如你不能把一个叶子队列变成父队列或者改变它的父队列。这种结构性变更需要重启ResourceManager。第三新增队列可以直接刷新生效。比如你新加了一个data_team.realtime队列refresh之后就能提交任务了。第四capacity之和的约束在刷新时也会检查。如果你改了某个队列的capacity导致同级队列加起来不等于100%刷新会失败。所以改容量的时候要一次性把所有相关队列都改好。4. 一套可直接参考的多队列配置模板4.1 场景假设与配置目标假设我们有一个中等规模的集群50个节点每节点16 vcore、64GB内存总计800 vcore、3200GB内存。有三个团队共用数据平台组、算法组、工程组。资源分配比例定为5:3:2。数据平台组内部再分批处理和即席查询两个子队列比例7:3。算法组内部分训练和推理两个子队列比例8:2。工程组不细分。配置目标各团队有基本的资源保障允许弹性借用空闲资源但不超过设定上限即席查询和推理服务有更高的优先级能快速拿到资源防止单个用户或单个任务独占队列资源4.2 capacity-scheduler.xml完整配置configuration !-- 基础配置 -- property nameyarn.scheduler.capacity.resource-calculator/name valueorg.apache.hadoop.yarn.util.resource.DominantResourceCalculator/value /property property nameyarn.scheduler.capacity.maximum-am-resource-percent/name value0.1/value /property property nameyarn.scheduler.capacity.node-locality-delay/name value40/value /property !-- 根队列 -- property nameyarn.scheduler.capacity.root.queues/name valuedata_team,algo_team,eng_team,default/value /property property nameyarn.scheduler.capacity.root.capacity/name value100/value /property !-- data_team 队列 -- property nameyarn.scheduler.capacity.root.data_team.capacity/name value50/value /property property nameyarn.scheduler.capacity.root.data_team.maximum-capacity/name value75/value /property property nameyarn.scheduler.capacity.root.data_team.user-limit-factor/name value1/value /property property nameyarn.scheduler.capacity.root.data_team.maximum-am-resource-percent/name value0.08/value /property property nameyarn.scheduler.capacity.root.data_team.queues/name valuebatch,adhoc/value /property !-- data_team.batch 子队列 -- property nameyarn.scheduler.capacity.root.data_team.batch.capacity/name value70/value /property property nameyarn.scheduler.capacity.root.data_team.batch.maximum-capacity/name value100/value /property property nameyarn.scheduler.capacity.root.data_team.batch.maximum-am-resource-percent/name value0.05/value /property !-- data_team.adhoc 子队列 -- property nameyarn.scheduler.capacity.root.data_team.adhoc.capacity/name value30/value /property property nameyarn.scheduler.capacity.root.data_team.adhoc.maximum-capacity/name value100/value /property property nameyarn.scheduler.capacity.root.data_team.adhoc.maximum-am-resource-percent/name value0.15/value /property !-- algo_team 队列 -- property nameyarn.scheduler.capacity.root.algo_team.capacity/name value30/value /property property nameyarn.scheduler.capacity.root.algo_team.maximum-capacity/name value60/value /property property nameyarn.scheduler.capacity.root.algo_team.user-limit-factor/name value1/value /property property nameyarn.scheduler.capacity.root.algo_team.queues/name valuetraining,inference/value /property !-- algo_team.training 子队列 -- property nameyarn.scheduler.capacity.root.algo_team.training.capacity/name value80/value /property property nameyarn.scheduler.capacity.root.algo_team.training.maximum-capacity/name value100/value /property property nameyarn.scheduler.capacity.root.algo_team.training.maximum-am-resource-percent/name value0.05/value /property !-- algo_team.inference 子队列 -- property nameyarn.scheduler.capacity.root.algo_team.inference.capacity/name value20/value /property property nameyarn.scheduler.capacity.root.algo_team.inference.maximum-capacity/name value100/value /property property nameyarn.scheduler.capacity.root.algo_team.inference.maximum-am-resource-percent/name value0.2/value /property !-- eng_team 队列 -- property nameyarn.scheduler.capacity.root.eng_team.capacity/name value20/value /property property nameyarn.scheduler.capacity.root.eng_team.maximum-capacity/name value40/value /property property nameyarn.scheduler.capacity.root.eng_team.user-limit-factor/name value1/value /property !-- default 队列 -- property nameyarn.scheduler.capacity.root.default.capacity/name value0/value /property property nameyarn.scheduler.capacity.root.default.maximum-capacity/name value10/value /property /configuration4.3 配置解读与关键决策说明这份配置里有几个决策点值得展开说。为什么default队列的capacity是0因为我不希望任务默认进default队列。设成0之后default队列没有保障资源只有在其他队列空闲时才能借用。这样做的目的是“逼迫”用户明确指定队列避免有人图省事不指定队列名结果任务跑到default里影响其他人。当然default的maximum-capacity设了10%保证它至少能跑起来一些小任务。为什么data_team.batch的maximum-am-resource-percent设了0.05批处理任务通常单个任务资源需求大、并发数少AM占用的资源比例应该低一些。设成5%意味着最多5%的队列资源用于AM剩下95%用于实际任务容器。如果设太高比如20%那当队列跑满时20%的资源被AM占着实际计算资源只有80%利用率会下降。为什么algo_team.inference的maximum-am-resource-percent设了0.2推理服务通常是很多个小任务长期驻留每个任务都需要一个AM。如果AM资源比例设太低并发任务数上不去。设成20%可以支持更多并发任务代价是实际计算资源比例降低。这是一个典型的权衡。为什么data_team的maximum-capacity是75%而不是100%因为data_team的capacity已经是50%了如果maximum-capacity设100%意味着它可以吃掉整个集群。虽然弹性共享是好事但data_team的任务通常跑得久一旦扩到100%其他队列再提交任务就很难拿到资源。设成75%是一个折中允许它弹性扩展50%的额外资源但不至于把其他队列逼死。子队列的capacity之和必须等于父队列的100%。比如data_team下面batch是70%、adhoc是30%加起来100%。这个约束在配置时YARN会检查。如果加起来不是100%刷新会失败。5. 配置生效后的验证与常见问题排查5.1 如何确认队列配置已经生效配置改完、执行yarn rmadmin -refreshQueues之后怎么确认配置真的生效了有几个方法。最直接的是访问ResourceManager的Web UI默认端口8088。在“Scheduler”页面可以看到队列树、每个队列的容量、已用资源、运行中的任务数等信息。如果队列树的结构和你配置的一致说明刷新成功了。另一个方法是用命令行yarn queue -status data_team.batch。这个命令会输出队列的当前状态包括capacity、maximum-capacity、当前使用的资源等。如果输出里的数值和你配置的一致就说明生效了。还有一个更底层的验证方式提交一个测试任务到指定队列然后观察它是否进入了正确的队列。可以用yarn application -list查看任务的队列信息。如果任务进了错误的队列说明队列名写错了或者配置没生效。5.2 刷新队列时的常见报错与处理refreshQueues最常见的报错是“capacity sum exceeds 100%”或者“queue not found”。前者是因为你改了某个队列的capacity导致同级队列加起来不等于100%。处理方法是检查所有同级队列的capacity确保它们的和是100。注意这里说的是同一父队列下的所有子队列不是整个集群的所有队列。另一个常见报错是“Cannot remove queue while it has running applications”。这是因为你试图删除一个还有任务在跑的队列。处理方法是先等任务跑完或者用yarn application -kill app_id杀掉任务然后再刷新。还有一个比较隐蔽的问题配置文件的XML格式错误。比如标签没闭合、属性值没加引号、特殊字符没转义等。这种错误在refresh的时候可能不会立刻报出来但会导致部分配置不生效。建议改完配置后用xmllint工具检查一下格式xmllint --noout capacity-scheduler.xml。如果没有输出说明格式正确。5.3 任务提交到错误队列的排查思路有时候配置看起来没问题但任务就是进不了指定的队列。排查思路是这样的第一步确认提交命令里的队列名和配置里的队列名完全一致。注意大小写、下划线、点号。data_team.batch和data_team.batch看起来一样但如果有隐藏字符或者大小写差异就会出问题。第二步确认队列确实是叶子队列。如果你提交到data_team父队列YARN会报错“Queue data_team is not a leaf queue”。只有叶子队列才能提交任务。第三步检查capacity-scheduler.xml是否真的被加载了。有时候改了配置文件但忘了执行refreshQueues或者改错了文件比如改的是yarn-site.xml而不是capacity-scheduler.xml。可以查看ResourceManager的日志搜索“capacity-scheduler”相关的加载信息。第四步如果用了队列映射queue mapping检查映射规则是否正确。队列映射可以根据用户或应用名自动把任务分配到指定队列。如果映射规则写错了任务会被分配到错误的队列。这个功能在capacity-scheduler.xml里通过yarn.scheduler.capacity.queue-mappings配置。5.4 资源利用率不达预期的可能原因配了多队列之后有时候会发现集群整体资源利用率反而下降了。比如每个队列都有空闲资源但任务就是调度不上去。可能的原因有几个原因一maximum-capacity设得太低。如果每个队列的maximum-capacity都设得比较保守当某个队列的任务暴增时它无法借用其他队列的空闲资源导致整体利用率上不去。解决方法是适当调高maximum-capacity或者开启抢占preemption。原因二AM资源比例限制太严。如果maximum-am-resource-percent设得太低并发任务数受限即使有资源也跑不了新任务。可以观察Web UI里AM占用的资源比例如果接近上限就适当调高。原因三队列内用户限制太严。user-limit-factor设成1意味着单个用户最多用满队列的capacity。如果队列里只有一个活跃用户而他提交的任务需要更多资源就会被限制住。可以适当调高这个值或者增加活跃用户数。原因四资源碎片化。如果集群里有很多小节点而任务需要的容器比较大可能出现“总资源够但单个节点资源不够”的情况。这时候需要调整容器的资源请求或者开启yarn.scheduler.capacity.node-locality-delay来等待合适的节点。5.5 抢占机制的配置与影响Capacity Scheduler支持抢占preemption当一个队列的资源低于其保障容量而另一个队列的资源超过其保障容量时调度器可以抢占后者的一部分容器分配给前者。这个机制保证了保障容量的严肃性但也会带来一些副作用。开启抢占需要配置yarn.scheduler.capacity.preemption.enabled为true并设置yarn.scheduler.capacity.preemption.monitoring-interval监控间隔默认10秒和yarn.scheduler.capacity.preemption.max_ignored_over_capacity允许超出的比例默认0.1等参数。抢占的副作用是被抢占的任务会被kill掉然后重新调度。对于长时间运行的批处理任务这意味着进度丢失需要重跑。所以开启抢占要谨慎最好配合任务的重试机制使用。我的建议是如果业务对任务完成时间不敏感可以开启抢占来保证资源隔离如果任务重跑成本很高就通过合理的capacity和maximum-capacity配置来避免资源争抢而不是依赖抢占。6. 几个我在实际配置中踩过的坑6.1 队列名里的点号引发的提交失败有一次配了一个队列叫data_team.batch提交任务的时候写的是-D mapreduce.job.queuenamedata_team.batch结果任务一直报“Queue not found”。查了半天配置队列明明存在。后来发现是提交命令里的点号被shell解析了——在某些shell环境下点号有特殊含义。解决办法是用引号把队列名包起来-D mapreduce.job.queuenamedata_team.batch。这个坑很隐蔽因为不是所有shell都会出问题但一旦遇到就很难排查。6.2 忘记配CPU资源导致调度不均前面提到过默认的DefaultResourceCalculator只看内存。我一开始配队列的时候只配了内存的capacity没配CPU也没改resource-calculator。结果发现一个奇怪的现象有些节点内存用满了但CPU还很闲有些节点CPU跑满了但内存还有富余。原因是调度器只按内存分配容器不考虑CPU导致CPU和内存的使用不均衡。改成DominantResourceCalculator之后调度器会同时考虑两个维度资源利用率明显改善。6.3 动态刷新后配置没生效的排查有一次改了队列的capacity执行了refreshQueues命令返回成功但Web UI上看到的容量还是旧的。排查发现是因为ResourceManager有两个实例HA模式refreshQueues只刷新了Active的那个Standby的没刷新。等主备切换之后Standby变成Active用的还是旧配置。解决办法是在HA模式下需要对两个ResourceManager都执行刷新或者直接重启两个RM。这个坑在HA集群里很常见但文档里很少提到。6.4 子队列容量之和超过父队列的隐蔽错误配置子队列的时候我一度以为只要顶层队列加起来是100%就行子队列可以随便配。结果有一次把data_team下面的batch配了80%、adhoc配了40%加起来120%刷新直接报错。后来才明白每一级队列的子队列capacity之和都必须等于100%这是递归的约束。父队列的capacity是相对于它的父队列的子队列的capacity是相对于父队列的。所以batch的80%不是集群的80%而是data_team的80%。这个理解上的偏差导致我配错了好几次。6.5 默认队列不设限导致的任务堆积最开始配多队列的时候我保留了default队列的默认配置——capacity 100%、maximum-capacity 100%。结果发现有些用户图省事不指定队列任务全进了default把其他队列的资源都挤占了。后来把default的capacity改成0、maximum-capacity改成10%情况才好转。这个经验告诉我默认队列一定要设限不能让它成为“法外之地”。
阅读完成 · 觉得有帮助?