资讯动态

Yarn三种调度器详解:FIFO、容量调度器与公平调度器选型指南

发布时间:2026/10/20 14:28:56 来源:尧图企业网站定制
一个真实场景某天凌晨数据平台群里突然有人喊Hive任务全卡住了。打开ResourceManager页面一看队列所有Container全被一个跑了一整夜的超大Spark任务吃满其余部门提交的任务全部排队一个个ACCEPTED挂在列表里实际一个都没执行。集群还剩很多CPU但就是分不出去。这就是调度策略在真实集群里的模样——它不是某个藏在配置里的抽象概念而是直接决定谁的作业先跑、谁在高峰被赶出去、资源在多个团队之间怎么切的底层规则。搞大数据的人如果只懂读写Hive、写Spark代码却不了解Yarn调度器迟早要在生产集群上吃一次亏。这篇文章我把Yarn的三种调度算法——FIFO、容量调度器Capacity Scheduler、公平调度器Fair Scheduler完整讲一遍。不讲纯理论重点落在每种调度器到底怎么决策、配置里每个参数控制什么、在真实集群里会遇到什么样的问题。如果你是刚接手集群运维的工程师或者团队里正在为多个部门抢资源吵架的协调人这篇能帮你把方案说得清楚。1. 调度器的工作机制与核心矛盾队列、容量与公平1.1 从一次凌晨的任务堆积说起先回到开头那个场景里。当时一个超大规模聚合查询把整个默认队列占满了后面提交的任务连一个Container都拿不到。这个问题的根源不在于集群容量不足而在于调度器把所有请求都塞进了同一条通道且没有做时间或资源上的分片调度。这正是调度器存在的意义。一个Yarn集群通常被多个团队共享离线数仓跑Hive/Spark实时团队跑Flink算法团队跑TensorFlow训练。每个人提交的任务都想要资源但总资源是有限的。调度器的职责不是加速单任务而是给所有请求安排一个合理的资源分配顺序。Yarn的调度器工作在两个层次第一层是队列Queue级别的资源分配决定哪个队列可以获得多大比例的资源。第二层是任务Application级别的调度决定队列内部先满足哪个Application的Container申请。大部分生产集群的问题都出在这一层之间要么队列配得太粗团队之间互相干扰要么队列配得太细资源碎片严重导致每个任务都在等Container。1.2 调度不是谁能跑就谁跑而是一套配额协议如果你只把调度理解为谁先来谁先跑那遇到业务冲突时会非常被动。调度器的真正价值是在资源池内建立一套可以量化的分配协议约定三点默认拥有的资源额度Capacity、可以临时借用的上限Maximum Capacity、以及需要保证最低执行量的资源Minimum Resources。这三件事分别对应了Yarn现有调度器的三个关键行为逻辑调度器资源分配依据特点典型适用场景FIFO提交时间先后简单直接、无隔离单人使用、小集群测试环境容量调度器预定义容量配额强隔离、支持层级队列、可弹性超用多部门共享、离线任务与实时任务混跑公平调度器权重公平算法动态分配、支持抢占、任务更均衡交互式查询、任务类型多样、负载变化大在看配置之前先理解这三者的底层逻辑差异。否则直接开配capacity-scheduler.xml或fair-scheduler.xml很容易出现参数照抄了但集群还是乱跑的情况。2. FIFO调度器最简模型背后的致命短板2.1 FIFO的运行逻辑与历史背景FIFO是Hadoop早期版本的默认调度器全称First In First Out。它的核心逻辑跟队列这个名字一样简单所有任务按提交时间排成一个队前面的任务没有跑完之前后面的任务不能获得资源。从实现上看FIFO对每个Application都会试着分配一个Container。如果当前没有空闲资源这个Application就被挂到等待队列里调度器不会重新排列它们的优先级完全按先后顺序来。因为实现简单FIFO的开销很小适合部署在个人测试环境或单业务、单作业流的集群里。早期Hadoop集群大多数是单团队使用FIFO够用。但当一个集群开始承接多个部门的作业问题就暴露得很干脆。2.2 FIFO的致命短板阻塞与相互饿死我见过最典型的情况是数据仓库的一个凌晨全量刷新任务资源申请300个Container运行时间3到4小时。在这段时间里其他所有部门提交的小任务哪怕只需要一个Container也全部卡住。FIFO没有预占用或可超越机制先来的大任务就是霸占了整个集群。另一种更隐蔽的问题是队头阻塞。如果队首的应用因为资源申请不合理比如单Container内存申请过大一直起不来后面的任务即使只需要小资源也会全部跟着等。这种情况在FIFO下排查起来尤其费劲因为从调度器日志看所有任务都在正常排队。所以FIFO基本被生产环境抛弃除了保留在低版本Hadoop里作为兼容选项几乎没有人再主动配置它。学FIFO的真正价值是理解调度器最朴素的逻辑方便后面解释容量调度器在哪个环节做了改进。3. 容量调度器用容量配额把集群切成资源包厢3.1 队列模型给每个团队一间隔音包厢容量调度器是目前Yarn默认使用的调度器也是生产环境最常见的选择。它的核心变化是不再让所有任务挤在同一个队列里而是允许多级队列每个队列有自己独立的最小资源保障。你可以把集群想象成一个写字楼每个团队租了其中几间办公室办公室的大小对应容量配额。团队A占40%团队B占30%团队C占30%。每间办公室的房间内部再按业务拆分成更细的居住单元。这种层级队列的配置在hadoop目录下的capacity-scheduler.xml中定义。下面是一个常见的配置片段configuration property nameyarn.scheduler.capacity.root.queues/name valuedev,pro,BI/value /property property nameyarn.scheduler.capacity.root.dev.capacity/name value30/value /property property nameyarn.scheduler.capacity.root.pro.capacity/name value50/value /property property nameyarn.scheduler.capacity.root.BI.capacity/name value20/value /property property nameyarn.scheduler.capacity.root.dev.maximum-capacity/name value60/value /property /configurationcapacity的值是队列可以占用的集群资源的百分比所有兄弟队列的总和必须等于100。maximum-capacity表示这个队列最多能涨到多少默认值为-1即允许无限借用其他队列的空闲资源。3.2 capacity-scheduler.xml 里的核心配置与含义容量调度器的配置文件看起来字段不多但每个字段在真实集群里的行为都不同。挑几个高频、容易出问题的字段单独说明配置项作用常见误区yarn.scheduler.capacity.root.xxx.capacity该队列最小保证容量改这个值会影响同层其他队列的总和必须重新计算maximum-capacity该队列弹性增长上限忘记配置时默认无限容易让某个队列把集群占满maximum-am-resource-percentAM占用资源的最大比例设置过小会导致ApplicationMaster无法启动作业全部失败user-limit-factor单个用户最多占用队列资源倍数限制用户超额占用避免一人独吞队列state队列状态RUNNING / STOPPED滚动升级或迁移前要STOPPED需要排空队列我实际踩过最典型的坑是给某个队列的最大容量设了80%却忘了同一个父队列下还有另一个实时计算的队列。结果离线任务在夜间冲到80%之后实时计算队列的重要作业反而被挤到边缘Flink任务频繁发生Container超时失败。后来把两个队列的maximum-capacity统一下调到60%这种情况才消失。3.3 弹性与借用空闲资源该不该借给邻居容量调度器一个容易被忽略的特性是弹性借用。现在梳理一下前文场景深夜离线队列只有一半任务在跑实时队列提交了一个大任务。如果实时队列的maximum-capacity允许调度器会优先把离线队列的空闲容量分配给实时任务。这个机制本身很好问题是很多团队把maximum-capacity忘配了默认-1意味着可以借满整体资源。如果某次大任务同时出现在两个队列一个队列可能会因为另一个队列的超借迟迟等不到资源。因此生产环境务必为关键队列设置一个合理的maximum-capacity值既要保证峰值可用性也要防止被其他队列挤爆。借用触发之后如果离线队列突然新增任务容量调度器会等正在运行的Container自然结束并不会强制杀任务抢资源。这与公平调度器完全不同后面会讲到。3.4 容量调度器实际使用中的典型问题容量调度器在真实集群里最常见的三类问题排队时间暴涨队列容量配置过小任务长期处于ACCEPTED状态。通过REST API查看队列状态时会出现大量Pending Containers。解决办法是分析历史峰值重新规划容量比例。资源碎片化任务申请单个Container过大例如单Container 100GB内存队列虽然有大量小内存Container可用但因为单请求无法拆分任务永远得不到匹配出现有资源却跑不了的情况。这个在集群里非常常见后面调优部分细说。队列权限失控任何用户都能提交到pro队列导致核心业务被临时任务冲击。生产环境需要配合Yarn的ACL访问控制列表配置yarn.scheduler.capacity.root.pro.acl_submit_applications限制只有特定用户组能提任务。容量调度器适合业务边界清晰、队列资源比例相对固定的集群特别是离线和实时任务混跑的场景。它的设计哲学是保住底线允许弹性让高峰期的冲突在一个可控的范围内自行消化。4. 公平调度器用权重与抢占让资源动态流动4.1 公平不是平均分权重与最低资源保证公平调度器的设计目标比容量调度器更进一步它希望在各个队列之间动态平衡资源保证每个队列都尽量得到与其权重相称的资源份额。这里的公平不是所有队列平分而是按照当前活跃的队列数量和权重来动态计算。公平调度器有个专门的配置文件fair-scheduler.xml比容量调度器的配置模型更灵活。下面是一个典型配置allocations queue nameroot queue nameteamA minResources10000 mb,2 vcores/minResources maxResources50000 mb,20 vcores/maxResources weight2.0/weight /queue queue nameteamB minResources10000 mb,2 vcores/minResources maxResources30000 mb,10 vcores/maxResources weight1.0/weight /queue /queue user namedefault maxRunningApps100/maxRunningApps /user defaultQueueSchedulingPolicyfair/defaultQueueSchedulingPolicy /allocations这里面最关键的一项是weight。当有两个队列同时竞争资源时调度器会按权重比例分配。权重为2的队列在资源紧张时会比权重为1的队列多分配到大约两倍的份额。minResources则是保证资源类似容量调度器里的capacity。公平调度器真正的特点在于它会周期性重新计算每个运行队列的公平份额fair share并主动回收超出份额的Container。这个回收动作就是抢占Preemption。4.2 抢占机制把超用的资源请回来抢占是公平调度器与容量调度器最大的一个差异点。容量调度器宁愿让任务多等也基本不会强杀正在运行的Container公平调度器为了追求总体公平一旦发现有队列持续超过公平份额就会触发Container回收。抢占过程大致分三步调度器计算每个队列当前应该获得的公平份额。标记那些超出的Container作为可抢占对象。配置项yarn.scheduler.preemption.enabledtrue以及yarn.scheduler.preemption.threshold默认0.8表示队列使用超过公平份额80%时开始考虑抢占。调度器向ApplicationMaster发送抢占通知要求释放部分Container。对于大多数框架Spark、Flink直接杀死超出的Container是最常见的实现因此被抢占的任务需要走重试或容错机制才能从故障中恢复。我用公平调度器做过一次部署测试队列A有一个巨型批处理任务占用了60%资源而队列B的交互式查询任务迟迟拿不到Container。开启了抢占后最多5分钟队列B就拿到了一部分资源。整个集群确实更平衡了但对运行中的Spark任务而言Container被抢占会导致Stage重算整体效率可能不升反降。所以公平调度器并不是银弹。它更适合任务普遍短小、对资源延迟不敏感的场景比如混合负载的SQL引擎集群。如果核心业务是有状态的长任务开着抢占会非常痛苦。4.3 公平调度器的配置优先级与默认行为在实际配置公平调度器时有几个容易被忽略的默认行为如果没有显式配置队列的minResources公平调度器会默认给每个队列相同的份额这可能不符合「核心队列优先」的需求。fair调度器支持动态队列。提交任务时指定yarn.scheduler.fair.user-as-default-queuetrue的话会自动为每个用户Yan生一个队列若不加控制大量动态队列会把实际配额稀释。配置defaultQueueSchedulingPolicyfair/defaultQueueSchedulingPolicy时队列内部的任务也是按公平策略调度的而不是FIFO。这点对短小任务非常友好但同时会让队列内的资源频繁在不同任务间切换。公平调度器在需要短时间内抢占资源、或任务提交方很多且互不信任时表现很好。但在稳定的业务集群中我个人反而更喜欢容量调度器的稳定压过一切。5. 三种调度器的选型对比一张表找到适合你的集群5.1 调度器核心特性横向对照选调度器不是谁新选谁。我见过很多把公平调度器的preemption打开结果线上Spark任务全部失败率飙升的案例。选型之前先把三种调度器在几个关键维度上的差异看明白维度FIFO容量调度器公平调度器资源隔离无强按容量配额中等按动态份额抢占支持无无默认有队列层级不支持支持支持配置复杂度极低中等较高需要调权重、最小资源适合场景单人小集群生产集群默认选择多用户动态查询负载常见风险大任务阻塞所有任务队列配比不合理导致资源闲置抢占导致任务反复失败运维难度几乎为零中高从这套对比看生产集群里容量调度器的优先级是最高的。它的模型最稳定资源承诺最明确出问题也最好解释队列配置是多少就是多少。公平调度器可以作为补充适合交互式查询、临时SQL分析这类任务延时敏感的场景。5.2 真实集群的选型思路如果现在要搭建一个全新的Hadoop集群并且还没想好设计范围按以下思路判断基本不会出大错单团队使用且任务量不大用FIFO配置最少没有调度开销。多团队共享有明确的部门边界核心任务是批处理和准实时用容量调度器配多级队列容量按部门估算划分。集群面向大量临时用户如数据分析师任务短小频繁高峰难预测用公平调度器以用户为单位建动态队列开启抢占但要配合客户端重试机制。混合场景默认容量调度器。把实时任务和非实时任务分到不同队列实时队列的maximum-capacity设置得很高确保流量高峰可以抢到离线队列的空闲资源。选型完成之后接下来的重点就是调参和运维。6. 调度器参数调优与生产问题排查6.1 资源粒度的设计为什么任务排队但集群空闲很多集群的问题不是配置了哪种调度器而是任务根本申请不到合适的Container。这个问题要从Yarn的资源粒度说起。每个提交到Yarn上的任务都需要通过ApplicationMaster申请容器。容器的大小由任务自身决定比如Spark的每个Executor会按照spark.executor.memory和spark.executor.cores申请资源。如果任务申请100GB内存的Execuor而集群实际可用的单机资源只有30GB那么无论调度器怎么排这个任务都会一直卡在资源申请阶段。解决办法有三个层面单容器上限在yarn-site.xml中配置yarn.scheduler.maximum-allocation-mb和yarn.scheduler.maximum-allocation-vcores控制任何容器不能超过这个值。任务侧控制Spark任务一般设置单个Executor内存不超过可用NodeManager内存的一半。队列侧控制容量调度器里的maximum-capacity也同时限制单个队列的资源上限可以间接控制队列内任务申请的容器大小。我遇到过最离谱的集群问题是某个数据抽取任务在代码里把Spark Executor的core数写成了32但实际上每台机器只有16核。结果任务一直处于ACCEPTED状态但ResourceManager里所有节点的资源利用率都是0。这种问题对调度器来说是完全合理的调度器只是满足申请并没有义务帮任务做资源降级。6.2 长时间排队时的标准排查链路如果线上出现任务全部排队利用下面的步骤可以很快锁定原因先看ResourceManager UI的Scheduler页面确认每个队列当前的Used Capacity、Pending、Active任务数。如果队列已满看具体是哪个队列占用。如果所有队列都很空那就是任务申请的Container资源超过节点单个容器上限检查任务的Spark/Flink参数。调用yarn application -appStates RUNNING -list观察某些任务的状态是否一直处于ACCEPTED。用yarn logs -applicationId查看单个Application在等待资源时ApplicationMaster打的日志通常会有类似1 containers pending on 100 nodes的记录这个数字很能说明问题。如果在公平调度器下出现反复失败优先检查是否触发了抢占看ResourceManager日志中的Preemption标记。整个排查链路的核心概念是先分清楚资源不够还是资源请求畸形然后再进入调度器配置排查。6.3 队列ACL与动态队列的权限控制在调度器配置里队列的权限控制经常被忽略。同一个集群里有人误把大任务提交到核心业务队列导致SLA被拖垮的情况太常见了。容量调度器通过ACL控制谁能提交、谁能管理队列。配置项是property nameyarn.scheduler.capacity.root.pro.acl_submit_applications/name valuedata_biz/value /property property nameyarn.scheduler.capacity.root.pro.acl_administer_queue/name valueadmin_group/value /property这里的value可以是Linux用户或用户组用空格分隔。如果希望队列完全公开可以写成*。生产环境建议至少核心业务队列不要用通配符否则前面的容量规划全都等于白做。公平调度器下的动态队列要特别注意。默认行为是每个用户都会自动获得一个以用户名命名的队列这会绕过原本规划好的队列体系。若不需要动态队列把yarn.scheduler.fair.user-as-default-queue设为false同时把所有任务都引导到规划好的固定队列中。6.4 调度器选型变更前的准备集群在容量调度器和公平调度器之间切换本身并不复杂。在yarn-site.xml中设置yarn.resourcemanager.scheduler.class为org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler或org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler重启ResourceManager即可生效。但切换之后带来的行为变化影响面非常大。我分享一个真实的经验某次把集群从公平调度器切到容量调度器原本按用户自动隔离的动态队列全部失效所有任务都落到default队列结果第二天大量任务因为资源争夺失败。切换前没有做充分的队列映射规划几乎必然引发事故。切换前至少要列出三件事当前有哪些活跃队列、每个队列承载哪些业务、目标调度器的容量配额怎么拆分。还有一点切换过程中ResourceManager重启会导致所有正在运行的任务ApplicationMaster重新向新RM注册部分有状态任务需要额外验证容错能力。7. 调度器与多租户架构把队列设计成团队资源说明书7.1 队列层级设计的实践心得说实话调度器配置本身花不了多少时间真正花时间的是想清楚这个队列到底给谁用、最多用多少、高峰期谁让路。我自己倾向于在队列设计阶段就写一份简短的资源说明书包含三类信息队列对应的业务域、最小保证资源、峰值可借用上限。比如下面这个层级root ├── offline # 离线批处理Spark/Hive任务 │ ├── etl # 核心数仓管线容量20%max 40% │ └── adhoc # 临时分析容量10%max 30% ├── realtime # Flink实时任务容量40%max 70% └── default # 兜底队列容量30%max 100%这种设计的核心用意是把集群变成一个有阶级但可流动的资源池核心ETL有配额保障实时任务在高峰期可以通过max占比获得足够资源而知识库的临时查询永远不会挤爆核心管线。7.2 队列优先级与优先级剥夺引入队列之后还涉及一个经常被忽略的点跨队列的优先级是不存在的。无论配置了多复杂的队列结构调度器只负责队列与队列之间的资源分配而不负责跨队列比较两个任务的优先级。想实现跨队列的优先级控制只能靠调整容量、最大容量、抢占这些参数来实现。而数量调度器在队列内部使用的默认策略依然是FIFO如果你想队列内能优先跑某些任务需要配合yarn.scheduler.capacity.queue-priority等参数设置或在任务提交参数里设置优先级标记。否则队列内的执行顺序依旧先来后到。8. 写在最后Yarn调度器这个模块看起来只是配置几个XML文件但背后的设计逻辑直接决定了一个大数据集群在多业务场景下的稳定性和资源利用率。FIFO教会我们无隔离的单任务执行有多脆弱容量调度器让我们看到用强制配额换稳定产出的价值公平调度器则展示了动态平衡与抢占的柔性管理思路。根据我的经验刚开始接触大数据集群的工程师很容易陷入抄一份capacity-scheduler.xml就收工的幻觉。但调度策略不是一个一劳永逸的配置项它更像一份需要持续修订的租约随着业务团队扩张、任务形态改变、实时流量突增资源配比也需要周期性复盘和调整。最后说一个实操上的小技巧如果集群里已经运行了比较多的重要任务在做调度器参数变更时建议先在测试环境完整跑一遍选型切换观察任务从提交到Container分配的平均延迟。延迟从几秒变成几十秒或者频繁出现Container被抢占日志都是配置需要回调的信号。选调度器总会有取舍但有了明确指标取舍就不是拍脑袋了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑