延迟、优先级与 LIFO 任务
BullMQ 默认按 FIFO(先进先出)处理等待任务,但实际业务还会需要“稍后执行”“紧急任务先执行”或“最新状态优先”。这些需求分别对应 delay、priority 和 lifo。
延迟任务
delay 的单位是毫秒。任务先进入 delayed 状态,等待时间到达后才具备被 Worker 处理的条件。
import { Queue } from "bullmq";
interface ReminderJobData {
userId: string;
message: string;
}
const reminderQueue = new Queue<ReminderJobData>("reminder", {
connection: { host: "127.0.0.1", port: 6379 },
});
await reminderQueue.add(
"send-reminder",
{ userId: "user-42", message: "订单即将关闭" },
{ delay: 10 * 60 * 1_000 },
);
延迟任务适合短期提醒、超时检查、失败后补偿等场景。长期、大规模日历调度还应评估专门的调度系统。
在指定时间之后执行
const targetTime = new Date("2030-07-03T10:30:00+08:00");
const delay = Math.max(0, targetTime.getTime() - Date.now());
await reminderQueue.add(
"send-reminder",
{ userId: "user-42", message: "预约时间已到" },
{ delay },
);
使用带时区的 ISO 时间,避免不同服务器时区造成歧义。
延迟时间不是精确执行时间
delay: 5000 表示任务至少延迟约 5 秒,并不保证第 5000 毫秒准时开始。Worker 忙碌、队列积压、同一时间到期的任务数量都会带来额外等待。
只有处于 delayed 状态的任务才能使用 job.changeDelay(newDelay) 修改延迟;新值从调用时刻重新计算。
FIFO 与 LIFO
默认 FIFO 让先进入的任务先被处理,适合订单导入、账单处理等强调公平和时间顺序的任务。
await reminderQueue.add("send-reminder", {
userId: "user-1",
message: "第一条",
});
设置 lifo: true 后,新任务会放到等待队列前部:
await reminderQueue.add(
"refresh-preview",
{ userId: "user-1", message: "使用最新配置刷新预览" },
{ lifo: true },
);
LIFO 适合“最新结果价值更高”的刷新、预览和同步任务。但如果任务持续高速进入,旧任务可能长期得不到执行,因此不要将它用于要求公平或必须按顺序完成的业务。
优先级任务
优先级范围为 1 到 2_097_151,数字越小,优先级越高:
await reminderQueue.add(
"send-reminder",
{ userId: "vip-user", message: "重要通知" },
{ priority: 1 },
);
await reminderQueue.add(
"send-reminder",
{ userId: "normal-user", message: "普通通知" },
{ priority: 100 },
);
几个容易误解的规则:
- 数字小的先处理。
- 相同优先级内部使用 FIFO。
priority: 0表示没有优先级。- 未设置优先级的普通任务会在已设置优先级的任务之前处理。
因此,如果队列要统一使用优先级,最好所有任务都明确设置非零优先级。不要混用“有优先级”和“无优先级”任务后再假设 priority: 1 一定最先执行。
优先级任务的插入复杂度为 O(log n),比普通任务更慢。只为真正需要业务分级的队列启用,不要把连续数值当作精细调度器。
修改和观察优先级
const job = await reminderQueue.add(
"send-reminder",
{ userId: "user-9", message: "等待升级" },
{ priority: 100 },
);
await job.changePriority({ priority: 2 });
const prioritizedJobs = await reminderQueue.getPrioritized(0, 9);
const counts = await reminderQueue.getCountsPerPriority([0, 1, 2, 100]);
console.log(prioritizedJobs.length, counts);
changePriority({ lifo: true }) 也可将等待中的任务调整为 LIFO 行为。
如何选择
| 需求 | 选择 |
|---|---|
| 保持提交顺序和公平性 | FIFO(默认) |
| 到某个时间后才允许执行 | delay |
| 明确的紧急、普通、低优先级分层 | priority |
| 最新任务比旧任务更有价值 | lifo |
| 周期性执行 | Job Scheduler,参见第 09 章 |
实用技巧与最佳实践
- 队列名应反映同一类资源约束,不要把完全不同的任务硬塞进一个优先级体系。
- 延迟任务的业务数据中保存目标时间,便于诊断“为什么现在才执行”。
- 对超时取消类任务,在 Worker 中再次检查业务状态;用户可能已提前完成操作。
- 设计有限的优先级档位,例如
10/50/100,比散落的魔法数字更易维护。 - 监控低优先级任务等待时间,防止长期饥饿。
- 不依赖毫秒级准时;BullMQ 是任务队列,不是实时系统。
本章小结
delay 控制任务最早何时可执行,priority 改变不同等级任务的顺序,lifo 让新任务先于旧任务。选择排序策略时,应同时考虑公平性、积压、性能和业务是否允许旧任务被推迟。