
先讲一段真实经历。接手集群运维后没多长时间我就被半夜值班电话吵醒过推荐组的定时任务堆了三个小时没跑完数据仓库那边正在跑月度全量重算把整个集群的内存和核全部吃光连实时任务的写入链路都在超时报警。打开ResourceManager页面一看所有作业挤在一个默认队列里谁先提交谁就占资源没有任何隔离和优先级可言。那次之后我才真正意识到YARN调度器和多队列配置不是加几个参数的事而是整个集群资源治理的地基。很多接触过Hadoop生态的朋友都会碰到这个主题但容易把它理解成调一个配置文件、指定几个队列名就完事。实际上从YARN调度器的三种实现选型到队列层级怎么设计、容量比例怎么算、ACL怎么配、客户端怎么提交到指定队列再到配置上线后那些让人挠头的隐藏坑整条链路有一堆细节。这篇文章想把这条链路从头到尾捋一遍基于我自己在生产集群里的实操经验说清楚配置逻辑和排障方法。先说明一下这里聊的YARN是Hadoop生态里的Yet Another Resource Negotiator是HDFS和MapReduce、Spark、Flink这些分布式计算框架共用的一层资源调度中枢不少人搜YARN的时候会跑偏到前端的JavaScript包管理器那完全是另外一码事。本文面向的读者是大数据平台工程师、运维、数据开发以及任何需要在集群上跑批处理任务、又不想让任务之间互相打架的人。1. 先搞懂YARN调度器在调度什么资源池、Container与队列的关系1.1 一个没配多队列的YARN集群问题出在哪很多人对YARN调度器的第一印象是排队器任务来了就排队前面跑完了后面接着上。这个说法对了一半但它掩盖了最关键的部分——YARN调度器做的不是排队而是资源分配决策。没有多队列配置时所有用户提交的应用都进入root.default这一个队列。这会产生几个非常实际的问题一个大任务比如全量重算把集群所有可用资源申请走之后后面提交的小任务哪怕只需求1个核、512MB内存也必须等着因为资源池见底了。不同团队、不同业务线之间没有任何隔离墙。你没法对重要业务优先这件事做任何干预。也没有办法限制某个用户或某个团队能占多少资源。写了个死循环的Spark任务或者有人误提了一个会无限申请Executor的任务整个集群都可能被拖垮。我遇到的凌晨事故本质就是这些问题叠加在一起的结果。任务本身没有问题问题出在调度层面没有任何按队列切分资源的机制。1.2 调度的最小单元Container与ApplicationMaster要理解调度器怎么工作先得把YARN架构里的几个角色分清楚ResourceManagerRM全局资源管理者里面内置了Scheduler组件负责接收各个应用提交的资源申请并做出分配决策。NodeManagerNM每台节点上的代理负责管理本节点的资源按照RM的指示启动和回收Container。ApplicationMasterAM每个应用启动时YARN会先在一个Container里启动AM这个AM负责向RM申请后续的资源也就是Executitor或者Map/Reduce Task需要的Container并协调任务执行。Container资源分配的最小单位包含一定量的内存和虚拟核。可以把RM想象成一家酒店的中央预订系统NM是各楼层的管家Container就是清扫干净、可以入住的房间。客户也就是Application提交订单后第一步要先开一间房给领队住这个领队就是ApplicationMaster领队入住后再跟中央预订系统申请更多房间给团队成员。调度器就坐在中央预订系统里。它维护着一套资源账本记录每个队列的已用资源、剩余资源、保证资源以及所有等待中的资源申请。每当有NodeManager上报资源释放或者有新的应用提交进来调度器就会跑一遍分配逻辑决定把哪些Container分给哪个应用。这里有一个经常被忽视的细节YARN调度器分配资源时内存和虚拟核是成对考虑的。如果集群总内存是256GB、总核数是96核那么调度器在给队列和Container分资源时既要看内存占用量又要看核数占用量。有的队列可能内存用了很多但核还没用完有的队列则反过来。生产环境里优化队列容量时这两个维度都要盯着看只看内存一个指标很容易出偏差。1.3 为什么说多队列的本质是资源治理理解调度器在做什么之后多队列的意义就清晰了。多队列不是把任务简单分类而是把集群的物理资源池按比例切成若干个逻辑资源池每个队列有自己的保证容量、权限约束和资源上限。举个例子一个64核256GB内存的集群root下配了offline和realtime两个队列offline保证60%的资源realtime保证40%。那么哪怕offline队列里有几十个大任务在排队realtime队列提交的任务也能在属于自己的那40%里顺利跑起来不至于被饿死。反过来也一样realtime队列没人用的时候offline队列可以借用它的资源把集群利用率拉满。这套机制才是调度器配置逻辑的核心既要保证每个业务方在高峰期的低保又要允许闲置资源在队列之间流动不能做成静态硬分区。后面第3章和第4章会具体讲这个平衡怎么做。2. FIFO、Capacity、Fair三种调度器怎么选生产环境为什么普遍选CapacityYARN内置了三种调度器FIFO Scheduler、Capacity Scheduler、Fair Scheduler。很多人会纠结选哪个其实在真实生产环境里答案是非常明确的——大部分离线数据湖/数仓集群都选择Capacity Scheduler。下面把三种实现的特点和适用场景说透。2.1 FIFO与Fair Scheduler决策逻辑与它们的确定性问题FIFO Scheduler逻辑最简单就是先到先得。第一个进来独占全部资源跑完再给第二个。这种调度器只适合实验环境或者单用户的小集群因为一旦混跑多个业务方一个耗时长的大任务就能把后面所有作业全部堵死没法做任何隔离。Fair Scheduler的思路则是想让集群资源在多个任务之间均匀分配。它引入了Pool资源池的概念调度器会统计当前Running的任务数尽量让每个任务分到差不多的资源。它还支持DRF主导资源公平策略在内存和核两个维度之间做公平性平衡。看起来Fair Scheduler很民主但生产环境中用得反而少核心原因是它的资源分配波动性大。因为公平性是靠动态重算维持的当一个任务完成退出时它的资源会重新分给其他正在运行的作业这会导致同一个Spark任务在运行中不断拿到新资源执行速度忽快忽慢对于有SLA服务等级协议要求的业务来说非常不友好。尤其当任务规模差距很大时一个大作业和一个很小的作业放在同一个池子小作业虽然能公平地抢到资源但大作业的执行时间会被拖得非常不可控。Fair Scheduler在流式任务和离线任务混部、且对延迟不敏感的场景下还有一席之地但对绝大多数需要明确配额和多租户隔离的数仓集群它不是首选。2.2 Capacity Scheduler的核心设计按百分比切分带弹性Capacity Scheduler的设计目标正好补齐了Fair的短板给每个队列一个明确的保证容量百分比同时允许队列在资源空闲时临时借用其他队列的容量。它的关键参数有三个capacity队列的保证容量百分比同层级所有队列之和为100%。这个值表示至少能分到这么多是SLA的基石。maximum-capacity队列最多能用到多少资源默认-1表示不设上限。它是弹性的开关允许高优先级队列在别人闲置时冲到100%。user-limit-factor单个用户最多能占用队列资源的倍数用来防止队列内的单用户霸占。这套设计非常贴合公司内部多团队共用集群的典型场景每个团队有保证的保底段位高峰期互不干扰低谷期谁的队列闲着别人可以借过来跑既不违反配额又能拉高整体利用率。2.3 三种调度器横向对比与选型结论调度器分配维度隔离能力资源弹性配置复杂度典型场景FIFO按提交顺序无无无需配置单用户测试环境Fair Scheduler按运行任务数/权重动态均分弱靠Pool动态算强但波动大中等混合负载、无强SLA、实时离线混部实验Capacity Scheduler按队列容量百分比强强且可控中等偏高生产数仓/多团队共享集群/有SLA需求所以结论很直接生产环境里有多个团队、多个业务线共用集群时无脑选Capacity Scheduler基本不会错。它提供的确定性、配额隔离和可预期性是Fair那种动态均分给不了的。如果你的集群只有你自己一个人用那FIFO或默认配置也无所谓但如果像绝大多数公司一样大数据平台是公共基础设施那多队列的Capacity Scheduler就是你绕不开的配置。3. 队列怎么划、容量怎么算多队列设计阶段的几个关键决策配置文件改起来很快真正费脑子的是设计阶段。队列树长什么样、每个队列给多少、用户怎么映射、权限怎么控制这些决策直接决定上线后好不好用。3.1 队列树怎么设计两级到三级足够别为了复杂而复杂一个常见的设计误区是把队列树做得很深很细什么root.offline.data.etl.monthly五六层下去配置和排查成本都翻倍。我的经验是生产集群保持两级到三级层级最合理。比如这样的结构root.offline离线批处理跑定时数仓任务、报表任务。root.offline.etl核心ETL优先保障。root.offline.adhoc即席查询、临时跑数优先级低。root.realtime实时链路相关任务包括Flink和流式写入。root.test测试与开发队列配额给最小资源避免测试任务影响生产。设计原则是先按业务类型或SLA等级分一级再按作业性质分二级。不建议按团队组织结构照搬因为组织经常调整但队列设计要相对稳定。一级队列给哪个团队用可以在ACL里配置而不是在队列树上体现。3.2 容量比例怎么算参考历史用量、高峰错峰和缓冲队列容量分配没有唯一正确答案但有个可复制的测算方法第一步从YARN的Metrics或RM UI导出一段时间内各业务线的资源使用量曲线算出峰值和均值。 第二步判断业务间是否存在错峰。比如数仓任务一般集中在凌晨算法团队的任务集中在白天两者天然有错峰效果那么队列容量不一定要按峰值总和给满可以稍微压一点通过弹性机制互相借。 第三步给长期跑不满的临时查询队列一个合理的低保值通常是5%到10%保证调度器能给这个队列的基础任务分配容器。 第四步也是容易被忽略的root下一定要留一个default或者测试队列否则有些框架的默认队列参数写死成default任务会被拒收。我通常会做一个简单的容量规划表把每个队列的名称、保证容量、最大容量、主要业务方、是否允许借用别人容量都列出来评审通过后再落到配置里。这一步花不了多少时间但能避免上线之后反复调整。3.3 用户映射、ACL与单用户上限不配权限的队列等于没隔离队列容量配好了但没有权限控制等于白配。因为如果所有用户都能往所有队列提交任务那某个用户嫌自己队列资源不够偷偷把任务提到高优先级队列隔离就失效了。Capacity Scheduler里通过这几个参数控制权限property nameyarn.scheduler.capacity.root.offline.acl_submit_applications/name valueteam_etl,yarn/value /property property nameyarn.scheduler.capacity.root.offline.acl_administer_queue/name valueadmin/value /propertyacl_submit_applications的值是逗号分隔的用户或用户组。这里有一个生产环境里踩过的坑直接写用户名容易但人员变动后要频繁改配置建议按用户组来管理。Hadoop的组映射从Linux用户组或LDAP里来把用户归到对应的组里之后ACL只需要写组名运维压力小很多。另外yarn.scheduler.capacity.queue-mappings可以把用户自动映射到指定队列property nameyarn.scheduler.capacity.queue-mappings/name valueu:user_a:offline.adhoc,u:%user:offline.adhoc/value /property这样指定用户提交任务时如果没有显式指定队列就会自动进入映射的队列避免任务因为没指定队列而全堆到default里。除了ACL还有一个容易被忽略的user-limit-factorproperty nameyarn.scheduler.capacity.root.offline.user-limit-factor/name value2/value /property这个参数控制单个用户在队列内最多可以使用保证容量的几倍。比如offline队列保证容量是50%如果不设限制队列里如果只有一个用户提交任务他可以独占整个集群的50%设成2的话他最多也只能在条件允许时用到50%乘2等于100%——注意这个100%还受maximum-capacity约束。合理设置user-limit-factor可以防止某个用户写了个贪心任务就把整个队列的资源全吸走生产建议设为1或2。4. 从配置到落地capability-scheduler.xml全量配置与验证设计做完到动手配置环节。我不打算只给一个迷你示例直接给一份我在多个生产集群上用过的完整配置骨架并逐项说明含义。4.1 可以直接套用的capacity-scheduler.xml示例configuration property nameyarn.scheduler.capacity.root.queues/name valueoffline,realtime,adhoc,test/value /property !-- offline 队列核心离线任务 -- property nameyarn.scheduler.capacity.root.offline.capacity/name value50/value /property property nameyarn.scheduler.capacity.root.offline.maximum-capacity/name value100/value /property property nameyarn.scheduler.capacity.root.offline.user-limit-factor/name value2/value /property property nameyarn.scheduler.capacity.root.offline.acl_submit_applications/name valuegroup_offline,yarn/value /property property nameyarn.scheduler.capacity.root.offline.acl_administer_queue/name valueadmin/value /property !-- offline 下挂两个子队列 -- property nameyarn.scheduler.capacity.root.offline.queues/name valueetl,adhoc/value /property property nameyarn.scheduler.capacity.root.offline.etl.capacity/name value70/value /property property nameyarn.scheduler.capacity.root.offline.etl.maximum-capacity/name value100/value /property property nameyarn.scheduler.capacity.root.offline.adhoc.capacity/name value30/value /property property nameyarn.scheduler.capacity.root.offline.adhoc.maximum-capacity/name value80/value /property !-- realtime 队列实时计算 -- property nameyarn.scheduler.capacity.root.realtime.capacity/name value30/value /property property nameyarn.scheduler.capacity.root.realtime.maximum-capacity/name value100/value /property property nameyarn.scheduler.capacity.root.realtime.acl_submit_applications/name valuegroup_realtime,yarn/value /property property nameyarn.scheduler.capacity.root.realtime.acl_administer_queue/name valueadmin/value /property !-- adhoc 队列临时作业限制上限 -- property nameyarn.scheduler.capacity.root.adhoc.capacity/name value10/value /property property nameyarn.scheduler.capacity.root.adhoc.maximum-capacity/name value30/value /property property nameyarn.scheduler.capacity.root.adhoc.acl_submit_applications/name valuegroup_adhoc,yarn/value /property !-- test 队列 -- property nameyarn.scheduler.capacity.root.test.capacity/name value10/value /property property nameyarn.scheduler.capacity.root.test.maximum-capacity/name value50/value /property property nameyarn.scheduler.capacity.root.test.acl_submit_applications/name valuegroup_test,yarn/value /property !-- AM 资源占比 -- property nameyarn.scheduler.capacity.maximum-am-resource-percent/name value0.2/value /property /configuration注意几个要点root.offline.capacity是50root.realtime是30root.adhoc是10root.test是10同一层级加起来正好100。这是硬规则不够100或超过100刷新配置直接报错。root.offline下面的etl和adhoc容量7030等于100验证的是offline这一层内部的分配比例。maximum-capacity设置为100意味着该队列在别人闲置时可以借用集群全部资源adhoc设置成30则意味着临时任务最多只能用到30%防止临时作业把集群冲垮。这组设计才是真正的弹性与隔离的平衡。ACL里一定要包含yarn用户因为很多守护进程比如Spark的history server提交某些应用、部分调度系统用的就是这个用户把它排在ACL外面会导致平台组件提交任务被拒。4.2 加载配置与生效验证不用重启集群改完capacity-scheduler.xml后不用重启RMYARN提供了热加载命令yarn rmadmin -refreshQueues这个命令会让RM重新读取调度器配置。执行后如果配置语法有问题终端会立即报错比如capacity is not enough或者标签拼写错误。这条命令在生产上相对安全但也不要随便反复刷。我遇到过有人写了一个sh脚本循环刷新结果把正在跑任务的大量Container给挤掉了因为调度器配置变化在某些情况下会重新触发容器抢占逻辑。刷新后建议做三步验证打开RM Web UI进入/cluster/scheduler页面能看到每个队列的当前资源使用量、最大资源量、排队应用数和运行中的应用数。执行yarn queue -status offline能输出队列的详细状态。有JMX监控的话看CapacityScheduler的相关Metric确认队列的capacity与maximumCapacity已经变成新值。4.3 客户端提交MR、Spark、Flink怎么指定队列配置好队列之后客户端提交时要把任务挂到指定队列里。不同计算框架的参数不太一样MapReduce任务hadoop jar job.jar com.example.MyJob -Dmapreduce.job.queuenameoffline.etlSpark任务spark-submit \ --master yarn \ --deploy-mode cluster \ --queue offline.etl \ --driver-memory 4g \ --executor-memory 8g \ --executor-cores 4 \ --num-executors 20 \ app.jar也可以在spark-defaults.conf里写spark.yarn.queueoffline.etlFlink on YARN任务flink run \ -m yarn-cluster \ -yid application_xxx \ -D yarn.application.queueoffline.etl \ app.jar如果客户端没有显式指定队列任务就会进入默认队列也就是root.default。这就是为什么队列树设计里我坚持要留一个默认队列的原因总会有框架或者脚本忘记带队列参数没有default队列时这些任务会直接提交失败。顺便解答热搜词里的那个高频问题Spark on YARN提交是不是只需要一个Spark客户端就行了。答案是理论上是的。Spark提交到YARN时客户端只需要能访问到YARN的ResourceManager地址、能读写HDFS或对象存储来上传依赖jar包YARN NodeManager在启动Container时会从HDFS拉取Spark相关的分发包并自举。也就是说不需要在每台节点上都安装Spark。但实际生产里我还是建议在一个固定的网关机上统一安装与集群版本严格的Spark客户端原因不是技术必须而是版本管理、配置统一和问题排查都方便得多不然A同学用Spark 3.1的客户端、B同学用Spark 3.3的客户端互相之间容易出现底层协议兼容问题。4.4 提交后如何确认任务真的落在了目标队列这一步很多人漏掉。任务提交成功不等于队列生效尤其在配了用户映射或默认队列时你以为任务进了offline.etl实际可能在default里跑了大半天。验证方法# 查看所有运行中的应用及其队列 yarn application -list -appStates RUNNING # 查看某个具体应用的详情 yarn application -appId application_1699999999999_0001 -appStates ALL输出里的Queue字段会明确显示该应用所在的队列名。也可以在RM UI的应用详情页上看还能看到这个应用的AM资源是发在哪个队列下的。如果发现任务不在预期队列里优先检查客户端提交参数是不是拼写错误比如队列名写成了offline/etl而不是offline.etl以及ACL是否允许当前用户向这个队列提交。5. 多队列生产排障经验ACCEPTED不启动、ACL拒绝、容量不弹逐个拆解配置配完、任务能跑通不代表没有隐患。我整理了几条在多个集群里反复遇到的坑每一条都给出完整的排查链路而不是直接告诉你改哪个参数。5.1 任务一直处于ACCEPTED但起不来优先查AM资源与全局AM占比现象是任务提交到队列后状态一直是ACCEPTED既不失败也不启动。打开RM UI的Scheduler页面看到应用的AM Container一直在等待资源。排查链路是这样的第一步看这个队列当前剩余资源是多少。如果剩余资源大于AM申请的内存和核说明不是没有资源而是调度器侧还有其他限制。 第二步看全局的AM资源占比参数。yarn.scheduler.capacity.maximum-am-resource-percent默认在部分版本里是0.1也就是最多10%的资源能用来启动AM。如果集群里同时有大量Spark任务在提交AM占用的总资源超过这个比例新任务的AM就只能等待表现为一直ACCEPTED。这个坑最常见的触发场景是数据开发早高峰集中提交几百个Spark任务每个任务都要起一个AM瞬间把AM资源额度占满后面所有任务全部排队等AM。解决办法有两个方向一是把maximum-am-resource-percent调大比如从0.1调到0.2或者0.3二是从源头减少AM的并发量比如修改客户端的提交策略让任务错峰提交。注意只调AM比例而不加限制会有风险AM本身也是Container如果AM把资源占掉一大块留给真实计算Executor的资源就会变少任务反而更慢。参数要跟集群的稳定任务量匹配。5.2 配了ACL后提交报AccessControlException多半是用户组映射问题现象是任务提交直接失败日志里报org.apache.hadoop.security.AccessControlException后面跟着一长串队列路径。很多人第一反应是ACL配错了但真正的原因往往是用户组映射没生效。YARN判断用户属于哪个组依赖NodeManager和RM上的hadoop.security.group.mapping配置。如果你配的是用户组名而系统里那个用户实际不在对应的组里ACL自然不通过。我踩过一次很典型的坑配置里写group_offline但新入职的同学用户user_b在Linux里的主组是shared副组里才包含group_offline。在部分Hadoop版本和某些组映射实现下YARN只识别了用户的主组导致提交被拒。排查链路先确认客户端机器上执行id user_b看用户的实际组。登录到RM节点执行hadoop dfs -getconf相关的组映射匹配方式或者直接用hadoop org.apache.hadoop.security.ShellBasedUnixGroupsMapping测试。如果组没问题再看ACL字符串里是否有空格、全角逗号等问题。YARN的ACL解析是逗号分隔空格会被解析成用户名的一部分这是非常低级的错误但确实发生过。最稳妥的方案是ACL同时支持组名和用户名但优先用组名然后专门安排一个客户端代理用户统一提交所有任务ACL里把代理用户加进去。5.3 最大容量没设对弹性集群变成了静态分区有些人配置队列时只设置了capacitymaximum-capacity保持了默认的-1表示不设上限。这其实也可以因为不设上限意味着这个队列可以借用所有闲置资源。但另一类坑是把maximum-capacity设置得太死。比如某团队为了严格保障给offline队列设了maximum-capacity60想着说好给60就是60这样做的直接后果是即使realtime队列完全空闲offline队列也无法使用多余的那40%整集群资源利用率上不去。我自己更推荐的做法是:核心SLA队列设maximum-capacity100临时或低优先级队列设一个合理的上限比如30~50。这样既保证高优队列能充分借用资源又限制低优队列在高峰期喧宾夺主。如果集群已经配好了但发现资源利用率上不去先用RM UI看队列的Maximum Resource是不是等于它的Guaranteed Resource。如果两者相等说明没有弹性去把对应队列的maximum-capacity调大然后yarn rmadmin -refreshQueues。改完观察一两天集群总体利用率一般会有比较明显的改善。5.4 user-limit-factor过大或过小带来的两个极端user-limit-factor这个参数很有迷惑性因为它控制的是单用户最多能用队列保证容量的几倍但实际效果取决于队列里同时有多少用户提交任务。如果队列只有一个用户在跑任务user-limit-factor1时他最多只能用到队列保证容量的大小另外那部分即使闲置也不行user-limit-factor2时他可以在别人没有占用时用到2倍但因为还受maximum-capacity约束实际很难顶满。我在生产环境里的建议是核心业务队列设2低优队列设1。设2是为了让核心业务在低谷期能利用闲置资源把队列的弹性做出来设1是为了防止临时任务用户单个任务就把资源拉爆。5.5 配置刷新的血泪教训先备份、看输出、查日志最后说一个运维习惯的问题。yarn rmadmin -refreshQueues虽然可以热加载但它的容错并不完美。我遇到过几个情况XML文件标签写错了刷新时报莫名其妙的NPERM的日志里才看得到真正的解析错误。刷新命令执行成功了但集群里部分NodeManager上的缓存队列信息没同步需要观察一段时间。所以我现在每次改capacity-scheduler.xml之前一定会先备份一份带时间戳的文件改完后执行刷新命令不要只看命令是否返回0还要看RM的yarn-resourcemanager.log里有没有WARN或者ERROR最后再回到RM UI的Scheduler页面对照检查每个队列的容量是否和预期一致。这个备份看日志UI核对三步法能规避90%以上的低级事故。最后再分享一个从运维视角看的体会做集群调度配置这么多年我有一个很深的感受多队列配置本身是在写一套资源分配的规则但真正决定这套规则好不好的不是XML语法而是设计者对业务的理解。容量比例定得合不合理、ACL用户组划得对不对、哪个队列该有弹性、哪个队列该限制——这些决策如果不结合每条业务线的任务量、执行时间和SLA来谈配置得再规范也只是纸上谈兵。我在新接一个集群时会先和各个业务方拉一次资源需求清单再花两周时间把RM UI里各业务的资源使用曲线导出来最后才动手写配置。前面这些慢功夫看起来不产出东西但真正上线之后你会发现后续的调整频率会低很多。再配合yarn top这种命令时不时盯一下实时资源占用和RM UI队列页面的定期巡检多队列这套体系基本就稳了。如果你也在规划或者优化集群的调度器配置建议不要一下子把队列树搞得太复杂先从一个核心离线队列、一个实时队列、一个临时队列起步跑顺了再加节点。等这套逻辑在你的集群里验证稳定了再往更细的业务分层走。调度器配置没有最完美答案只有最适合你这批任务的答案。