批处理调度模型: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(最低)
但优先级调度有个经典问题——优先级反转。低优先级任务拿着高优先级任务需要的资源,高优先级任务反而被堵住了。我在做分布式锁调度时就踩过这个坑。
优先级反转的典型场景:
- 任务 A(高优先级)需要资源 R
- 任务 C(低优先级)正拿着资源 R
- 任务 B(中优先级)抢占了 C 的 CPU,C 没法释放 R
- A 只能干等,B 反而先跑完了
解决方案:优先级继承协议——当 C 持有 A 需要的资源时,C 临时继承 A 的优先级,直到释放资源。
优先级调度的优缺点:
| 优点 | 缺点 |
|---|---|
| 灵活,能按业务需求定制 | 低优先级任务可能被饿死 |
| 支持抢占,响应快 | 优先级反转问题需要额外处理 |
| 适合混合负载场景 | 优先级设置不合理反而降低效率 |
我的建议:优先级别设太多级。3-5 级就够了,级数太多反而增加调度开销和配置复杂度。我见过一个系统设了 20 级优先级,最后运维人员自己都搞不清该用哪一级。
三种模型对比
我把这三种模型放在一起对比,方便你快速选型:
| 维度 | FIFO | SJF | 优先级 |
|---|---|---|---|
| 实现复杂度 | 低 | 中 | 中高 |
| 平均等待时间 | 高 | 低 | 中 |
| 公平性 | 高 | 低(长任务吃亏) | 中(看优先级设置) |
| 适用场景 | 任务长度相近、无优先级需求 | 短任务多、可预估执行时间 | 业务有明确优先级、混合负载 |
| 典型问题 | 队头阻塞 | 长任务饿死 | 优先级反转 |
调度模型关系图
下面这张图展示了三种模型的核心逻辑和它们之间的关系:
实际生产中的选择建议
说了这么多理论,到底怎么选?我个人的经验是:
- 别迷信单一模型。大多数生产系统用的是混合策略——比如优先级队列里,同一优先级内部用 FIFO 或 SJF。
- 加兜底机制。不管用哪种模型,都要防止任务被无限期饿死。我习惯加一个「最大等待时间」的硬限制。
- 动态调整。静态的优先级或预估时间往往不准。我建议引入反馈机制——根据任务实际执行情况动态调整它的优先级或预估时间。
一个小技巧:如果你不确定用哪种模型,可以先从 FIFO 起步。等系统跑起来后,收集任务执行时间的分布数据,再决定要不要切到 SJF 或优先级模型。别一开始就搞复杂了,调度模型是可以演进的。