ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Slurm主调度器深度解析:优先级、回填与排障实战

Slurm主调度器深度解析:优先级、回填与排障实战 做集群运维这些年“我的作业为什么排在别人后面”是日常被问到最多的问题。刚开始我也只知道甩一句“任务优先级不够”后来为了搞清楚 Slurm 到底怎么决策的把主调度Main Scheduler相关的配置、日志和源码路径反复啃了几遍才建立起一套能指导日常排查的完整认知。这篇文章就把主调度这条线彻底讲透。内容会覆盖调度循环怎么跑、优先级怎么算、资源怎么选、回填怎么“见缝插针”以及我踩过坑之后整理出来的一套排障方法。不管你是刚接手超算集群的运维还是被用户排队问题反复折磨的 HPC 管理员这篇都值得收着慢慢看。1. 从一次“作业排队”说起主调度到底管到了哪一步1.1 一次 sbatch 提交后后台发生了什么很多人对调度器的理解停留在“作业提交后等它跑完就轮到我了”但实际过程远没有那么简单。你执行sbatch job.sh之后用户端的sbatch命令会把作业描述发送给控制节点上的slurmctld这是一个常驻守护进程也是整个集群的大脑。slurmctld接收作业后先做语法校验、资源申请合法性校验、QOS 限制校验然后把作业放进指定分区的 PENDING 队列。从入队那一刻开始主调度器就登场了。主调度器不是一个独立的进程它本质上是slurmctld内部的一段调度逻辑周期性运行。它会遍历 PENDING 队列对每个作业重新计算优先级然后按优先级从高到低排序再尝试为排序靠前的作业分配计算资源。如果资源足够就分配节点、建立slurmd与slurmstepd之间的连接作业进入 RUNNING 状态如果资源不足作业继续留在队列里等待下一轮调度。这里有个容易混淆的概念调度器负责“决策”但它不负责“搬数据”。作业脚本、输入文件、容器镜像这些数据流转是slurmctld通过 RPC 协议通知计算节点去做的主调度器只关心资源匹配和决策输出。把决策和传输分开理解后面排查调度延迟的时候会少走很多弯路。1.2 两条调度链路立即调度与周期调度主调度器的运作可以拆成两条链路。第一条是立即调度链路。当新作业入队、已有作业结束、节点状态发生变化、优先级被手动调整等事件发生的时候slurmctld会触发一次“尝试调度”。这是事件驱动机制目的是尽快响应变化让空出来的资源第一时间被利用。比如你手动scancel掉一个占着 32 节点的大作业后面排队的作业理论上不需要等调度周期事件触发的立即调度会马上尝试填补资源空缺。第二条是周期调度链路。slurmctld会在配置项SchedulerInterval指定的周期内默认通常是 2 秒扫一遍整个队列重新计算所有 PENDING 作业的优先级处理公平共享Fairshare随时间衰减后的变化并重新尝试调度。周期调度是为了兜底防止事件驱动在某些异常场景下漏掉作业比如调度器刚完成一轮调度时有新事件进来但资源仍不满足下一轮周期调度就会再次检查。理解了这两条链路你就会明白一个道理调度器不是“来一个作业调度一个”而是“事件来了立刻尝试调度同时每隔几秒全量重算一遍”。这个设计背后是为了兼顾响应速度和全局公平性代价是slurmctld主进程会持续消耗 CPU集群越大调度循环的单轮耗时越长。这也是后面我们讲“调度卡顿”时最先要怀疑的地方。2. 优先级计算决定“谁先跑”的核心算法2.1 multifactor 优先级到底是什么主调度器对作业排序的依据是 Job Priority 值这个值由PriorityTypepriority/multifactor这个插件计算出来。所谓 multifactor就是“多因子”把作业的年龄、公平共享、作业大小、分区、QOS 等维度映射成可以相加的数值。典型公式是JobPriority WeightJobAge * AgeFactor WeightFairshare * FairshareFactor WeightJobSize * JobSizeFactor WeightPartition * PartitionFactor WeightQOS * QOSFactor注意每个因子通常都会被归一化到 0 到 1 之间而不是直接用原始值。比如 AgeFactor 不是“已经等了 30 分钟”直接参与运算而是用等待时间除以PriorityMaxAge最大有效排队时间得到 0 到 1 之间的比例。FairshareFactor 也不是直接用 fairshare 百分比而是基于用户或账户的公平共享配额换算成 0 到 1 的衰减系数。这样做的目的是让不同量纲的因子可以统一加权相加权重项Weight*才真正反映管理员对不同维度的重视程度。很多刚入门的朋友容易犯一个错以为把PriorityWeightAge调高就一定能解决“老年作业被饿死”的问题。实际操作中你会发现如果PriorityMaxAge设置得过大AgeFactor 爬升非常慢权重再高也起不了作用。我一般习惯把PriorityMaxAge设置为 7 天左右让年龄因子在 3 到 5 天内就能达到接近 1 的水平这样“等得久的作业”才能获得真实的优势。2.2 Fairshare 账本公平共享怎么算出来的公平共享是 Slurm 调度策略里最复杂、也最容易被误解的部分。它的核心不是“每个用户分了多少 CPU 核”而是基于历史使用量的“债务关系”。Slurm 为每个账户和用户维护一个历史利用率账本当用户过去一段时间占用的资源比较多他的 FairshareFactor 就会下降未来优先级会降低反之用得少的用户会获得较高的 FairshareFactor。这个账本有两个关键参数PriorityDecayHalfLife和PriorityUsageResetPeriod。PriorityDecayHalfLife控制历史用量“遗忘”的速度单位是小时。比如设置为 14 天那么 14 天前的资源用量在账本中的权重会衰减一半。这个参数决定了公平性的时间窗口窗口太短会让集群变成“近 3 天谁用得多谁靠后”窗口太长会让长期不活跃的用户持续占优。PriorityUsageResetPeriod则控制账本多久彻底归零一次。可以是NONE永不重置也可以设置为DAILY、WEEKLY、MONTHLY或者更长的YEARLY。生产环境中我建议根据业务周期来设置如果团队是按月度项目制跑计算把重置周期设为MONTHLY通常比较合理如果是长年稳定运行的平台保持NONE配合衰减就够用。这里还要提一个细节Fairshare 是分层的。Slurm 优先保障层级结构中的上层账户配额再逐层下探到用户。配置里如果不对slurm.conf中的AccountingStorageExternalHost或sacctmgr中的层级账户做合理规划Fairshare 就是一笔糊涂账。很多团队以为配好了 Fairshare实际上所有用户挂在同一个根账户下公平性完全没体现出来这种情况我见过太多次了。2.3 优先级权重案例从实验集群到生产集群的取舍权重配置没有标准答案只能根据场景取舍。我以两套集群为例帮你建立直觉。第一套是 20 节点以内的实验集群用户以学生和科研人员为主作业时长一般不超过 4 小时。这种场景下用户对“等多久能看到结果”敏感但整体体量小不需要复杂问责机制。我倾向于把PriorityWeightAge设为 1000PriorityWeightFairshare设为 500PriorityWeightJobSize设为 100PriorityWeightPartition设为 1000PriorityWeightQOS设为 1000。原因很简单让等得久的作业尽快跑完同时保留一定的公平性约束避免少数用户刷屏式占满队列。第二套是 200 节点以上的生产集群有多个课题组共用业务包括短时间测试作业和动辄数百节点、跑一周的仿真作业。这种场景下公平性是第一诉求作业大小和年龄其次。我会把PriorityWeightFairshare提到 2000把PriorityWeightAge降到 800PriorityWeightJobSize降到 200。同时配合PreemptModeREQUEUE抢占低优先级作业保证高优先级 QOS 的作业能及时获得资源。权重调整不是改完就完事。每调一次我都建议观察一周的squeue和sshare输出统计一下 PENDING 作业的排队时间和用户分布。如果某一类作业长期排不进去说明权重比例失衡需要再微调。记住调权重是在“等得久的作业”“用得多的用户”“作业特别大的请求”之间找平衡永远没有一个参数能同时让所有人满意。3. 资源选择作业能被调度到哪几类节点上3.1 SelectType 与资源粒度优先级算完了主调度器还要回答另一个问题这个作业能不能在某个节点上放得下这就轮到资源选择插件上场。Slurm 默认的SelectTypeselect/cons_tres也就是可消耗资源方式它允许我们把 CPU、内存、GPU、MIC 卡等资源细粒度地分配给不同作业。在 cons_tres 模式下资源粒度由SelectTypeParameters控制。常见的参数包括CR_Core、CR_CPU、CR_Memory、CR_Socket_Memory、CR_Core_Memory等。其中CR_Core_Memory是我最常用的组合意思是按核分配 CPU同时每个核还要划走一部分内存。这种模式下一个节点如果配了 64 核、256G 内存那么 4 核作业申请 16G 内存时调度器会认为它占了 4 个核和对应内存不会出现“内存超分但 CPU 空闲”或者“CPU 占满但内存完全没用上”的尴尬。资源选择的另一个关键点是节点粒度。作业可以通过--nodes2申请多个节点这会走“整节点分配”逻辑也可以通过--ntasks-per-node搭配--cpus-per-task细化到单节点内部的资源布局。主调度器在尝试放置作业时会先检查有哪些节点满足分区约束再把这些节点的空闲资源汇总判断请求是否被满足。这是个匹配过程有点像拼积木积木的形状就是节点上已经分配出去的作业碎片。3.2 分区、QOS 与资源约束的叠加分区Partition和 QOS 是资源选择之外的两道过滤网。分区决定作业“可以在哪些节点上跑”QOS 决定作业“能够用多少资源”。两者叠加后主调度器会在候选节点列表里排除掉不满足配置的节点这也是很多人忽略的排队原因。举个例子集群里有一个GPU分区节点都带 8 张 A100。如果用户作业不指定--partitionGPU即使 GPU 分区全空作业也不会被调度到那边。类似地QOS 里如果对MaxNodesPerJob设置了 16那么用户申请 32 节点的作业无论优先级多高主调度器都会直接拒绝启动而不是让它排队等资源。这种失败不是资源不足而是策略限制。实际排查时我最常用的一条命令是scontrol show job jobid重点看输出里的Partition、QOS、Priority、Reason字段。如果Reason显示的是QOSMaxNodesPerJobLimit、AssocMaxNodesPerJobLimit这类信息说明作业是被策略挡住了不要浪费时间去看节点资源。如果Reason是Resources再去查节点空闲情况。3.3 回填在不饿死高优作业的前提下“见缝插针”默认的SchedulerTypesched/backfill让主调度器在排完高优先级作业之后额外执行一轮回填尝试。回填的意图很聪明如果现在排在最前面的高优作业申请 64 个节点但集群当前只有 32 个节点空闲按常规逻辑这 64 节点的作业得等全部节点凑齐才能跑其余低优作业即使能跑 30 个节点也会被高优作业“挡住”造成资源浪费。回填机制采用了反向策略。它会看一眼高优作业预估的启动时间然后尝试在“不影响高优作业准时启动”的前提下把剩余资源分配给低优作业。这样既保证高优作业不被推迟又让低优作业能先跑起来。这里有个隐含前提作业必须提供时间限制而且时间估计要靠谱。如果作业不写--time回填机制默认认为它会无限期运行自然不敢把它塞进高优作业前面这也是“很多短作业排队不动”的常见原因。回填的精细程度还取决于SchedulerParameters中的一些选项。默认配置是“经典回填”只回填第一个高优作业的空隙如果想进一步挖潜力可以开启depth_plug相关参数配合更深的回填深度。但这会显著增加调度器的计算量大集群上要谨慎开启。我的一般建议先保障--time规范落地再开启深度回填顺序反了只会让调度器忙死而收益甚微。4. 实操配置与调优一份可以直接上线的配置文件4.1 基础配置项与推荐值这里给出一段典型的slurm.conf调度相关配置配合注释解释每个参数的意义你可以根据自己的硬件规格和业务特征调整。# 调度器类型和周期 SchedulerTypesched/backfill SchedulerInterval2 SelectTypeselect/cons_tres SelectTypeParametersCR_Core_Memory # 优先级插件 PriorityTypepriority/multifactor PriorityCalcPeriod5 PriorityDecayHalfLife14-0 PriorityUsageResetPeriodMONTHLY PriorityMaxAge7-0 # 权重配置 PriorityWeightAge800 PriorityWeightFairshare2000 PriorityWeightJobSize200 PriorityWeightPartition1000 PriorityWeightQOS1000 # 回填参数 SchedulerParametersbf_interval30,bf_window1440SchedulerInterval2表示周期调度每 2 秒跑一次对大多数集群够用。如果控制节点 CPU 资源紧张可以适当调大到 5但作业响应速度会变慢。bf_interval30表示回填逻辑每 30 秒执行一次bf_window1440表示最多向前规划 1440 分钟1 天的作业启动窗口。窗口越长回填越精准但对调度器性能消耗越大。4.2 动态调参几步走Slurm 的一个优势是大部分调度参数可以动态修改不需要重启slurmctld。修改slurm.conf后逐行执行scontrol reconfigure这个命令会让slurmctld重新读取配置并应用到运行中的系统。对于PriorityWeight*、SchedulerInterval、SchedulerParameters等参数reconfigure 之后立即生效非常方便。但要注意有些参数虽然能动态加载也不会立即反映到已经排队的作业上。比如PriorityWeightFairshare调整后下一个调度周期会按新权重重算所有作业优先级如果只想立刻刷新批量作业的优先级可以用scontrol update job jobid prioritynew_priority强制设置一个临时优先级用来测试某个作业能否被插队调度。实测完记得恢复作业原有优先级否则会影响公平性。4.3 验证配置有没有生效改完配置不能看一眼就收工必须验证三件事插件是否加载、参数值是否正确、调度行为是否符合预期。第一件事查看插件状态scontrol show config | grep -E SchedulerType|PriorityType|SelectType如果没有加载到priority/multifactor那优先级计算根本不会按你的权重生效这类低级错误有时候真的会发生比如插件名打错或者编译时没启用。第二件事验证实际优先级sprio -lsprio命令会列出每个排队作业的年龄、公平共享、作业大小、分区、QOS 每个因子得分以及加权后的总分。我每次调权重后都会跑一遍观察不同作业的分数是否符合预期。比如一个等了两天的作业在AGE列的分数是否明显高于刚提交的作业。第三件事观察实际调度行为squeue -o %.18i %.9P %.8j %.8u %.2t %.10M %.6D %R确认高优先级作业是否先启动、回填作业是否确实利用了碎片资源。如果发现问题再结合日志深挖这部分我们放到下一节。5. 排障实录主调度“卡住”怎么办5.1 调度延迟的三类常见原因在实际运维里“作业排队排了半小时还没动”是最常见的工单。我总结下来主调度“卡住”的原因基本逃不出这三类。第一类是slurmctld负载过高导致调度周期被拉长。当集群节点数上了 500同时排队的作业数破千每一轮调度循环都要重算所有作业的优先级、检查所有节点的资源位图bitmapCPU 消耗会非常可观。如果控制节点上还有其他服务抢占 CPU调度周期可能会从 2 秒膨胀到 20 秒甚至更久用户体感就是“作业一直 PENDING”。第二类是资源碎片化严重调度器可以计算出作业能放的节点但怎么放都不满足。“可以放”和“放得下”是两回事当节点上残留了大量小作业占用的内存和核心一个需要整节点或大内存的作业可能长时间匹配不到合适位置。这种情况用户看到的Reason一般是Resources但用sinfo -o %n %e %a %G查看节点状态时又发现每个节点都有空闲资源会产生强烈的“资源明明有空却不让用”的错觉。第三类是回填逻辑导致“低优作业被高优作业锁死”。如果队列中有一个超大高优作业申请 256 节点回填算法为了不推迟它的启动时间会预留出所有它可能要用的节点。即便这个超大作业的预估启动时间还远碎片小作业也只能在缝隙里跑。这是回填机制本身的设计取舍不是故障。5.2 诊断命令速查遇到调度相关问题时我有一套固定的排查顺序按下面的命令一步步看# 查看所有排队作业的状态和原因 squeue -a -o %.18i %.9P %.8j %.8u %.2t %.10M %.6D %R # 查看单个作业的详细调度信息 scontrol show job jobid # 查看节点资源分配情况 sinfo -o %n %e %a %G %C %m # 查看各节点空闲资源和已分配资源的明细 scontrol show node # 查看控制节点负载和 slurmctld 进程状态 uptime ps aux | grep slurmctldsqueue -a输出中的Reason字段是第一个突破口。Resources表示资源不足Priority表示有更高优先级作业在前面BadConstraints表示节点约束条件无法满足Dependency表示依赖了其他作业还没结束QOSJobLimit等则表示被 QOS 限制卡住。Reason 能帮你把问题缩小到某一个方向再配合后续命令确认。5.3 一个真实案例的排查过程有一次线上集群反馈“GPU 分区排队异常”现象是分区里明明有 10 个节点空闲但很多作业就是 PENDING。我第一步看squeue -a发现 Reason 是Resources。实际上空闲节点并没有全部可用因为很多作业只申请了单张 GPU把 8 卡节点里的卡拆得七零八碎。这时我用sinfo -o %n %G %C %e逐节点看 GPU 状态发现每张卡的 Mem 也被计入了资源选择。回填算法认为某个节点虽然有空闲 GPU但显存被其他作业的缓存占满所以不能放进候选列表。这类问题的根因是用户经常通过--gpus-per-node1申请单卡却忘了申请对应显存导致显存被无限放大成资源约束。当时我们紧急处理是把这个分区的SelectTypeParameters里显存分配逻辑调整成更宽松的模式再在 QOS 里限制单用户最大 GPU 卡数从源头上减少碎片化。后面排队的短作业明显跑得快了很多。这个案例说明很多调度“卡住”的表象背后其实是资源选择和用户行为相互叠加的结果不能单靠调优先级解决。5.4 日志与性能指标怎么看如果以上命令都查不出明确原因就要看slurmctld日志。日志默认输出到slurmctld.log位置通常在/var/log/slurm/下。排查调度问题前先把日志级别临时调高# 在 slurm.conf 中加入或修改 SlurmctldDebugdebug3 SlurmctldLogFile/var/log/slurm/slurmctld.logdebug3会输出大量调度细节包括每个调度周期的时间戳、作业优先级计算过程、回填窗口计算、失败原因等。线上环境不建议长期开这么高排查结束后记得调回info或error否则日志会飞速增长。重点关注日志里的_slurm_rpc_submit_batch_job、job_complete、_slurm_rpc_job_alloc等关键节点配合系统时间戳可以算出单次调度耗时的分布。如果发现某个作业排队很久但迟迟没有调度尝试记录说明它可能被SchedulerParameters的某些限制如bf_window排除在回填范围之外。如果发现有大量RETRY或failed to select node记录那就是资源选择阶段出了问题回头去查分区配置和节点约束。性能指标方面slurmctld进程的 CPU 占用率和内存占用率是两个硬指标。可以用pidstat -p slurmctld_pid 1观察实时 CPU 使用如果持续接近 100%说明调度计算已经成为瓶颈。此时优先考虑优化作业粒度、减少同时排队的作业数量或者把控制节点升级到更强配置比盲目调高SchedulerInterval更有效。6. 写在最后我的几点心得主调度策略是一个典型的“牵一发而动全身”的系统。优先级、公平共享、回填、资源选择、QOS 这几个模块每一条都独立但又互相咬合。单独把参数调大调小很难直接解决问题每次调整前花几分钟把当前集群的作业画像、用户结构、节点资源利用率整体过一遍往往比反复调权重更高效。我个人的习惯是每一次变更都做成表格记录包括变更时间、参数旧值、新值、预期效果、实际观察结果。这样两三个月之后翻回去复盘能清晰地看到哪些调整真正起了作用哪些调整只是心理安慰。有了这份记录后面应对“作业排队异常”的工单几乎都能在十分钟内定位到可疑点而不是从头开始排查。最后一个小技巧周末或者夜间流量低的时候可以故意提交几个不同大小、不同时间限制的测试作业观察它们在不同优先级权重下的调度顺序再对照sprio -l的输出验证算法是否符合预期。亲手做一遍比你读十篇文档都管用。下一篇准备聊聊抢占和 QOS 的组合拳那是另一个能玩出花的深坑有实际案例的话我再一起放出来。
返回列表