批处理调度模型:FIFO、SJF 与优先级调度

调度模型这东西,说白了就是决定「下一个该谁上」。我早年做银行核心系统时,半夜跑批处理任务,调度策略选错了,整个批次能多跑两个小时。那时候我就意识到——调度模型不是理论游戏,是真金白银的运维成本。

这一章我们聊三种最基础的调度模型。它们虽然简单,但直到今天,很多生产系统还在用它们的变体。你想想看,理解透这三种模型,后面学复杂调度就轻松多了。

FIFO 调度模型(先来先服务)

FIFO 是最直观的模型。谁先进入队列,谁就先被执行。就像排队买奶茶,先来的人先拿到。

核心特征:

  • 非抢占式——任务一旦开始,就跑到结束
  • 队列顺序严格按到达时间排序
  • 实现极其简单,几乎零开销

我在项目中遇到过这样一个场景:某数据仓库的 ETL 任务用了 FIFO。白天跑得好好的,一到月底,有个超大的全量同步任务先进了队列,后面十几个小任务硬生生等了 40 分钟。业务方直接炸了。

FIFO 的优缺点:

优点 缺点
实现简单,无锁竞争 平均等待时间长
公平性高(按到达顺序) 有「队头阻塞」问题
适合短任务为主的场景 对长任务敏感,容易拖慢整体

注意:FIFO 在批处理系统中最大的坑是「饥饿放大」。一个长任务堵在前面,后面所有任务都跟着遭殃。我曾经见过一个生产事故——FIFO 队列里一个全量备份任务跑了 3 小时,导致后续的实时对账全部超时。

最短作业优先(SJF)模型

SJF 的思路很直接:谁执行时间短,谁先上。这就像超市收银台,买一瓶水的人先结账,买一购物车的人往后排。

为什么这么做?因为从数学上看,SJF 能最小化平均等待时间。我刚开始做调度系统时,觉得这模型太理想了——你怎么知道一个任务要跑多久?

嗯,这里要注意。SJF 有两种变体:

  • 非抢占式 SJF:任务开始后不打断,等它跑完再选下一个最短的
  • 抢占式 SJF(也叫 SRTF):新任务如果比当前任务剩余时间短,就抢过来

实际生产中,我们通常用历史执行时间来预估。比如一个任务过去 10 次平均跑了 5 分钟,那就拿这个当预估时间。

我的经验:预估时间别用简单平均。我习惯用 P90 或 P95 分位数,这样能避免偶尔的慢执行把预估带偏。举个例子,一个任务 9 次跑 1 分钟,1 次跑 10 分钟,平均是 1.9 分钟,但 P90 是 10 分钟——用后者做调度更安全。

SJF 的优缺点:

优点 缺点
平均等待时间理论上最优 需要预估执行时间,不准就白搭
短任务响应快 长任务可能被饿死(一直有短任务插队)
适合短任务密集的场景 抢占式实现复杂,有上下文切换开销

避坑指南:我曾经在一个离线计算平台上用过纯 SJF,结果有个长任务被饿了 6 个小时。后来加了「最大等待时间」兜底——如果一个任务在队列里等了超过 30 分钟,强制提升它的优先级。这个策略后来成了我们的标配。

优先级调度模型

优先级调度,就是给每个任务贴个标签——1 级、2 级、3 级……数字越小优先级越高(或者反过来,看你怎么定义)。调度器每次选优先级最高的任务执行。

这模型很灵活。你可以根据任务类型、业务重要性、SLA 要求来定优先级。比如:

  • 实时对账任务 → 优先级 1(最高)
  • 日终批量任务 → 优先级 5
  • 历史数据归档 → 优先级 10(最低)

但优先级调度有个经典问题——优先级反转。低优先级任务拿着高优先级任务需要的资源,高优先级任务反而被堵住了。我在做分布式锁调度时就踩过这个坑。

优先级反转的典型场景:

  1. 任务 A(高优先级)需要资源 R
  2. 任务 C(低优先级)正拿着资源 R
  3. 任务 B(中优先级)抢占了 C 的 CPU,C 没法释放 R
  4. A 只能干等,B 反而先跑完了

解决方案:优先级继承协议——当 C 持有 A 需要的资源时,C 临时继承 A 的优先级,直到释放资源。

优先级调度的优缺点:

优点 缺点
灵活,能按业务需求定制 低优先级任务可能被饿死
支持抢占,响应快 优先级反转问题需要额外处理
适合混合负载场景 优先级设置不合理反而降低效率

我的建议:优先级别设太多级。3-5 级就够了,级数太多反而增加调度开销和配置复杂度。我见过一个系统设了 20 级优先级,最后运维人员自己都搞不清该用哪一级。

三种模型对比

我把这三种模型放在一起对比,方便你快速选型:

维度 FIFO SJF 优先级
实现复杂度 低 中 中高
平均等待时间 高 低 中
公平性 高 低(长任务吃亏) 中(看优先级设置)
适用场景 任务长度相近、无优先级需求 短任务多、可预估执行时间 业务有明确优先级、混合负载
典型问题 队头阻塞 长任务饿死 优先级反转

调度模型关系图

下面这张图展示了三种模型的核心逻辑和它们之间的关系:

批处理调度模型关系图 批处理调度模型核心逻辑 调度器 FIFO 模型 先来先服务 SJF 模型 最短作业优先 优先级模型 按优先级调度 特性 • 非抢占式 • 按到达顺序 • 队头阻塞风险 特性 • 可抢占/非抢占 • 需预估时间 • 长任务可能饿死 特性 • 支持抢占 • 优先级反转 • 灵活配置 选型建议:看业务场景,没有银弹 短任务多 → SJF | 有优先级需求 → 优先级 | 简单场景 → FIFO

实际生产中的选择建议

说了这么多理论,到底怎么选?我个人的经验是:

  • 别迷信单一模型。大多数生产系统用的是混合策略——比如优先级队列里,同一优先级内部用 FIFO 或 SJF。
  • 加兜底机制。不管用哪种模型,都要防止任务被无限期饿死。我习惯加一个「最大等待时间」的硬限制。
  • 动态调整。静态的优先级或预估时间往往不准。我建议引入反馈机制——根据任务实际执行情况动态调整它的优先级或预估时间。

一个小技巧:如果你不确定用哪种模型,可以先从 FIFO 起步。等系统跑起来后,收集任务执行时间的分布数据,再决定要不要切到 SJF 或优先级模型。别一开始就搞复杂了,调度模型是可以演进的。