1. 从单队列到多队列:为什么容量调度器需要多队列配置
刚接触Hadoop那会儿,我图省事,整个集群就配了一个default队列,所有任务往里扔,谁抢到算谁的。小规模测试倒也没出过什么大问题,可一旦任务量上来,麻烦就来了——一个跑批任务把资源全占了,后面提交的交互式查询只能干等着,运维那边电话一个接一个。后来被逼着去啃YARN的调度器配置,才发现容量调度器(Capacity Scheduler)的多队列机制才是解决这类资源争抢的正解。
容量调度器是YARN里最常用的调度器之一,它的核心思路是把整个集群的资源切分成多个队列,每个队列拿到一个保证容量(capacity),同时允许在资源空闲时借用其他队列的闲置资源。这个设计的好处很直接:关键业务有保底资源,不会被其他任务饿死;非关键业务在空闲时段又能充分利用集群,不至于让机器闲着。说白了就是“保底+弹性”的组合拳。
多队列配置要解决的问题,本质上就是资源隔离与共享的平衡。你既不想让某个业务把集群吃干抹净,又不想让资源白白浪费。容量调度器通过队列层级、容量比例、最大容量上限、用户限制等一整套参数,把这件平衡的事做细了。适合谁来参考?我觉得三类人最需要:一是刚接手Hadoop集群、发现资源分配一团糟的运维;二是需要给不同团队或业务线划分资源的平台开发者;三是想搞明白YARN调度底层逻辑、准备调优的大数据工程师。下面我就按实际配置的顺序,把多队列这套东西拆开讲透。
2. 容量调度器的核心概念与多队列设计思路
2.1 队列层级与资源分配的基本逻辑
容量调度器支持树形队列结构,也就是队列下面还能挂子队列。这个设计不是摆设,它对应的是真实组织架构里的资源划分方式。比如一个公司有数据平台部和算法部,数据平台部下又分实时计算组和离线计算组,那队列就可以配成root下面挂data_platform和algorithm,data_platform下面再挂realtime和batch。资源从根队列往下逐层分配,每一层都有自己的容量比例。
这里有个关键点容易搞混:容量(capacity)是保证值,不是上限值。假设root总资源是100个虚拟核,队列A的capacity配了30%,那A至少能拿到30个核。但如果队列B只用了10%,A在需要的时候可以借走B闲置的部分,实际使用可能超过30%。真正限制A不能无限膨胀的是最大容量(maximum-capacity),这个参数才是硬上限。我见过不少配置把这两个概念弄反,结果要么资源借不出去,要么某个队列把集群占满。
资源分配还有一层层级公平的考量。当多个队列同时缺资源时,调度器会按容量比例来分配,而不是先到先得。比如A和B的capacity都是50%,同时有任务在等,那资源就五五开。但如果A已经用了60%,B只用了40%,新释放的资源会优先给B,直到两者回到比例平衡。这个机制保证了长期来看各队列拿到的资源趋近于配置比例。
2.2 为什么选择容量调度器而不是公平调度器
YARN里另一个常见选择是公平调度器(Fair Scheduler),两者都能做多队列,但设计哲学不同。公平调度器的核心是“动态平衡”,它追求的是任意时刻各队列资源占比接近配置权重,谁的资源少就优先给谁。容量调度器则更强调“保证容量”,先确保每个队列拿到承诺的那份,再谈借用。
选哪个取决于业务场景。如果集群里跑的都是同优先级任务,追求整体吞吐和响应速度,公平调度器可能更合适。但如果有明确的业务分级——比如核心报表任务必须按时完成,测试任务可以慢慢跑——那容量调度器的保底机制就更对路。我实际维护的集群里,核心业务队列的capacity配得比较高,maximum-capacity设成100%,保证高峰期能吃掉空闲资源;测试队列capacity低但maximum-capacity也放开,闲时能跑满。这种“核心保底、边缘弹性”的玩法,容量调度器支持得最自然。
还有一个实际因素是配置的直观性。容量调度器的参数命名和层级结构比较贴近运维的思维习惯,capacity、maximum-capacity、user-limit-factor这些参数一看就大概知道干什么。公平调度器的配置相对更灵活,但也更绕。对于刚上手YARN调度的团队,容量调度器的学习曲线更平缓。
2.3 多队列配置要提前想清楚的几件事
动手写配置文件之前,有几件事必须先想明白,否则配出来的队列大概率要返工。
第一是队列数量和层级深度。队列不是越多越好,每多一层就多一层调度开销,而且层级太深之后资源碎片化严重,大任务反而不好调度。我的经验是控制在三层以内,最底层队列数量别超过十个。如果业务线特别多,可以考虑用队列名做逻辑区分,而不是无限加层级。
第二是容量比例的分配依据。这个不能拍脑袋,得看历史资源使用数据。我一般会拉过去一个月的YARN资源监控,算各业务线的平均使用量和峰值使用量,再结合未来规划来定capacity。保证容量要覆盖平均使用量,maximum-capacity要能容纳峰值,这样既不会平时浪费,也不会高峰期卡死。
第三是用户和应用的限制策略。多队列解决了队列间的隔离,但队列内部如果某个用户提交大量任务,照样能把队列占满。这时候就需要user-limit-factor和maximum-am-resource-percent这类参数来兜底。user-limit-factor控制单个用户最多能用队列容量的几倍,默认是1,意思是不能超过队列保证容量。如果队列内部用户比较多,可以适当调大,但别超过2,否则隔离就形同虚设了。
3. 多队列配置实操:从capacity-scheduler.xml开始
3.1 配置文件结构与关键参数逐项解读
容量调度器的配置集中在capacity-scheduler.xml这个文件里,放在Hadoop配置目录下。它的参数命名有一套固定规则:yarn.scheduler.capacity.是前缀,后面跟队列路径和参数名。根队列用root表示,子队列用点号连接,比如root.data_platform.realtime。
先看几个全局参数。yarn.scheduler.capacity.root.queues定义根队列下有哪些子队列,多个队列用逗号分隔。yarn.scheduler.capacity.root.capacity是根队列的容量,固定为100。yarn.scheduler.capacity.maximum-am-resource-percent控制ApplicationMaster能占用的最大资源比例,默认0.1,意思是10%的资源留给AM,剩下的给实际任务。这个值在小集群里可以调小,大集群里可以适当调大,但一般不超过0.2。
队列级别的参数里,capacity和maximum-capacity是最核心的两个。capacity是保证容量,所有同级队列的capacity之和必须等于100。maximum-capacity是资源使用上限,可以等于100,表示允许借用所有空闲资源。还有一个user-limit-factor,控制单用户资源上限相对于队列capacity的倍数。maximum-am-resource-percent可以在队列级别覆盖全局值,控制该队列的AM资源占比。
资源分配还涉及资源计算器的选择。默认是DefaultResourceCalculator,只考虑内存;如果要用CPU资源做调度,得换成DominantResourceCalculator。这个选择影响capacity的计算方式,配之前要确认集群的资源类型。
3.2 一个三层队列结构的完整配置示例
假设有个集群,总资源是200GB内存、100个虚拟核。业务分两大块:数据平台和算法。数据平台下面再分实时和离线,算法下面分训练和推理。配置目标是这样:
- 数据平台整体保证40%资源,最高可用70%
- 算法整体保证60%资源,最高可用100%
- 数据平台内部,实时和离线各占一半保证容量
- 算法内部,训练占70%,推理占30%
对应的capacity-scheduler.xml配置如下:
<configuration> <property> <name>yarn.scheduler.capacity.root.queues</name> <value>data_platform,algorithm</value> </property> <property> <name>yarn.scheduler.capacity.root.data_platform.capacity</name> <value>40</value> </property> <property> <name>yarn.scheduler.capacity.root.data_platform.maximum-capacity</name> <value>70</value> </property> <property> <name>yarn.scheduler.capacity.root.data_platform.queues</name> <value>realtime,batch</value> </property> <property> <name>yarn.scheduler.capacity.root.data_platform.realtime.capacity</name> <value>50</value> </property> <property> <name>yarn.scheduler.capacity.root.data_platform.realtime.maximum-capacity</name> <value>100</value> </property> <property> <name>yarn.scheduler.capacity.root.data_platform.batch.capacity</name> <value>50</value> </property> <property> <name>yarn.scheduler.capacity.root.data_platform.batch.maximum-capacity</name> <value>100</value> </property> <property> <name>yarn.scheduler.capacity.root.algorithm.capacity</name> <value>60</value> </property> <property> <name>yarn.scheduler.capacity.root.algorithm.maximum-capacity</name> <value>100</value> </property> <property> <name>yarn.scheduler.capacity.root.algorithm.queues</name> <value>training,inference</value> </property> <property> <name>yarn.scheduler.capacity.root.algorithm.training.capacity</name> <value>70</value> </property> <property> <name>yarn.scheduler.capacity.root.algorithm.training.maximum-capacity</name> <value>100</value> </property> <property> <name>yarn.scheduler.capacity.root.algorithm.inference.capacity</name> <value>30</value> </property> <property> <name>yarn.scheduler.capacity.root.algorithm.inference.maximum-capacity</name> <value>100</value> </property> <property> <name>yarn.scheduler.capacity.maximum-am-resource-percent</name> <value>0.15</value> </property> <property> <name>yarn.scheduler.capacity.resource-calculator</name> <value>org.apache.hadoop.yarn.util.resource.DominantResourceCalculator</value> </property> </configuration>这里有几个细节值得说。子队列的capacity是相对于父队列的,不是相对于整个集群。比如realtime的50%是data_platform那40%里的一半,实际占集群20%。maximum-capacity同理,是相对于父队列的上限。另外,所有叶子队列的capacity之和必须等于100,中间层队列的capacity之和也要等于100,这个校验在启动时会做,配错了直接起不来。
3.3 配置生效与队列状态验证
改完配置文件后,需要重启ResourceManager或者用yarn rmadmin -refreshQueues做热刷新。热刷新不用重启集群,但有些参数改不了,比如队列的增删。我一般先在测试环境验证,确认没问题再上生产。
验证队列是否生效,有几个命令很实用。yarn queue -status <队列名>能看队列的当前状态,包括容量、已用资源、运行中应用数。yarn application -list -appStates ALL可以看所有应用跑在哪个队列。ResourceManager的Web UI里有个Scheduler页面,能直观看到各队列的资源占用和容量比例,排查问题的时候我基本都开着这个页面。
还有一个容易忽略的点:队列的ACL配置。默认情况下所有用户都能往任何队列提交任务,这显然不安全。通过yarn.scheduler.capacity.root.<队列名>.acl_submit_applications可以限制哪些用户或组能往该队列提交任务。格式是用户1,用户2 组1,组2,中间用空格分隔用户和组。这个配置在多团队共享集群时几乎是必须的,否则隔离就只是纸面上的。
4. 多队列运行中的典型问题与排查实录
4.1 队列资源借出去了收不回来怎么办
这是容量调度器多队列配置里最常见的问题。现象是某个队列平时用不满,资源被其他队列借走,等它自己需要资源时,借出去的部分迟迟不释放,导致任务排队。根本原因在于容量调度器不会强制抢占,它只能等借用的队列自己释放资源。
解决思路有几个。最直接的是配置抢占策略。YARN支持在容量调度器上开启抢占,通过yarn.scheduler.capacity.<队列路径>.disable_preemption设为false来启用。抢占的触发条件是某个队列的资源使用低于其保证容量一定时间,调度器会杀掉其他队列里低优先级的Container来释放资源。抢占有代价,被杀的Container对应的任务要重跑,所以一般只对核心队列开启,且设置合理的抢占等待时间。
另一个思路是调整maximum-capacity。如果某个队列经常借走大量资源不还,可以适当降低它的maximum-capacity,限制它能借的上限。比如把测试队列的maximum-capacity从100降到60,它最多只能用到60%的资源,剩下的留给其他队列。这个调整比较温和,不会杀任务,但会牺牲一些资源利用率。
还有一个实操技巧是用队列优先级配合。容量调度器支持队列优先级,高优先级队列在资源分配时有优势。把核心业务队列的优先级调高,即使资源被借走,新任务提交时也能更快拿到资源。优先级通过yarn.scheduler.capacity.root.<队列名>.priority配置,数值越小优先级越高。
4.2 队列容量之和超100导致的启动失败
这个坑我踩过不止一次。容量调度器在启动时会校验同级队列的capacity之和是否等于100,不等于就直接抛异常,ResourceManager起不来。报错信息一般是“Sum of capacities of child queues is not 100”。
问题往往出在层级计算上。比如root下面有两个队列A和B,A的capacity是40,B是60,加起来100没问题。但A下面又挂了A1和A2,如果A1配30、A2配30,加起来60,不等于100,就会报错。子队列的capacity之和必须等于100,跟父队列的capacity是多少无关。这个规则容易记混,配的时候要逐层检查。
还有一种情况是动态增删队列。热刷新不支持增删队列,只能改已有队列的参数。如果要加新队列,必须改配置文件后重启ResourceManager。重启前一定要确认所有同级队列的capacity之和是100,否则重启会失败,集群直接不可用。我的习惯是改完配置先用hadoop org.apache.hadoop.yarn.conf.YarnConfiguration之类的工具做一次配置校验,或者直接在测试环境重启验证。
4.3 单用户占满队列的排查与限制
队列内部如果某个用户提交了大量任务,把队列资源占满,其他用户就提交不进去了。这个问题在多团队共享队列时特别突出。排查方法很简单,在ResourceManager Web UI的Scheduler页面看队列的User Limit信息,或者用yarn queue -status看各用户的资源使用。
限制手段主要是user-limit-factor。这个参数控制单个用户最多能用队列capacity的几倍。默认是1,意思是用户最多用到队列的保证容量。如果队列capacity是40%,那单用户最多用40%的集群资源。调大到2,单用户最多能用80%。这个值不能设太大,否则队列隔离就没意义了。我一般设1.5到2之间,既给用户一些弹性,又不至于让一个人吃掉整个队列。
还有一个参数是minimum-user-limit-percent,它保证每个用户至少能拿到队列资源的某个百分比。比如设成10,意思是队列里至少有10个用户能同时使用资源,每个用户至少10%。这个参数在用户数不确定的场景下有用,能防止先到的用户把资源占完。实际配置时,user-limit-factor和minimum-user-limit-percent要配合使用,前者管上限,后者管下限。
4.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决措施 |
|---|---|---|---|
| ResourceManager启动失败,报容量之和不等于100 | 同级队列capacity之和不等于100 | 检查配置文件各层级capacity之和 | 调整capacity使同级之和为100 |
| 队列资源被借走不释放 | 容量调度器不强制抢占 | 查看队列资源使用和保证容量对比 | 开启抢占或降低借用队列maximum-capacity |
| 单用户占满队列 | user-limit-factor过大或未配置 | Web UI查看用户资源使用 | 调小user-limit-factor,配置minimum-user-limit-percent |
| 任务提交报队列不存在 | 队列未配置或ACL限制 | 检查队列配置和ACL | 添加队列配置或调整ACL |
| 热刷新后配置未生效 | 部分参数不支持热刷新 | 查看ResourceManager日志 | 重启ResourceManager |
| AM资源占比过高导致任务排队 | maximum-am-resource-percent过大 | 查看AM资源占用 | 调小该参数,一般0.1到0.15 |
5. 多队列配置的调优经验与长期维护建议
5.1 容量比例怎么定才合理
容量比例的设定没有标准答案,但有几个原则可以参考。保证容量覆盖平均使用量,这是底线。如果某个队列的平均使用量是集群的25%,那capacity至少配25%,否则平时就会经常触发借用,调度开销大。maximum-capacity覆盖峰值使用量,但不要轻易设100%,除非这个队列确实需要弹性到全集群。我一般把核心队列的maximum-capacity设成80到90,留一些余量给其他队列应急。
比例调整要有数据支撑。我每个月会拉一次YARN的资源使用报表,看各队列的实际使用率和排队情况。如果某个队列长期使用率低于capacity,说明配高了,可以往下调;如果经常触发maximum-capacity限制,说明配低了,需要往上调。调整前先在测试环境验证,确认不会导致其他队列资源不足。
还有一个经验是留一个弹性队列。不管业务怎么划分,我都会留一个capacity不高但maximum-capacity全开的队列,专门给临时任务和测试用。这个队列平时不怎么占资源,但需要的时候能借用空闲资源,不至于让临时任务没地方跑。
5.2 监控与告警要盯哪些指标
多队列跑起来之后,监控跟不上就是睁眼瞎。我重点盯几个指标:各队列的资源使用率,包括已用内存、已用vcore、运行中应用数;队列的排队应用数,排队数持续大于零说明资源不足;AM资源占比,超过配置的maximum-am-resource-percent就要注意;抢占次数,如果抢占频繁发生,说明容量配置可能不合理。
告警阈值我一般这样设:队列使用率持续10分钟超过maximum-capacity的90%就告警,排队应用数超过5个告警,抢占次数每小时超过3次告警。这些阈值可以根据集群规模和业务敏感度调整。监控工具用Prometheus加Grafana就能搭起来,YARN的Metrics接口暴露了这些数据。
5.3 队列调整的时机与操作节奏
队列配置不是配一次就完事,业务在变,队列也要跟着调。但调整不能太频繁,否则调度器刚适应又变了,反而影响稳定性。我的节奏是季度做一次大调整,月度做一次小调整。大调整涉及队列增删和容量比例重分配,需要走变更流程,在业务低峰期操作。小调整只改个别参数,比如user-limit-factor或maximum-am-resource-percent,可以热刷新,影响面小。
调整前一定要备份配置文件,并且记录调整前后的参数对比。出问题的时候能快速回滚。调整后观察至少一个完整业务周期,确认没有异常再做下一次调整。我见过有人一天改三次配置,结果调度器行为混乱,任务排队反而更严重。
5.4 几个容易忽略的细节
最后分享几个实操中容易忽略但影响不小的细节。队列名称不要用中文或特殊字符,虽然配置上可能支持,但Web UI和命令行工具显示会有问题,排查时很麻烦。队列的ACL要定期审查,人员变动后及时调整,避免离职人员还能提交任务。配置文件的版本管理要做好,每次变更都提交到版本库,方便追溯。
还有一个细节是资源计算器的选择要和集群资源类型匹配。如果集群只关心内存,用默认的DefaultResourceCalculator就行;如果CPU也是瓶颈,一定要换成DominantResourceCalculator,否则CPU被某个队列吃满,内存还有富余,调度器却认为资源充足,继续往那个队列派任务。这个坑我在一个CPU密集型集群里踩过,换了计算器之后问题才解决。
容量调度器的多队列配置,说到底是在资源隔离和利用效率之间找平衡。配得太死,资源浪费;配得太松,隔离失效。我的体会是,先按业务重要性划出大框架,再用监控数据慢慢微调,别指望一次配到完美。跑上几个月,积累一些实际数据,调整起来就有方向了。