延迟、优先级与 LIFO 任务

BullMQ 默认按 FIFO(先进先出)处理等待任务,但实际业务还会需要“稍后执行”“紧急任务先执行”或“最新状态优先”。这些需求分别对应 delayprioritylifo

延迟任务

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 适合“最新结果价值更高”的刷新、预览和同步任务。但如果任务持续高速进入,旧任务可能长期得不到执行,因此不要将它用于要求公平或必须按顺序完成的业务。

优先级任务

优先级范围为 12_097_151,数字越小,优先级越高:

await reminderQueue.add(
  "send-reminder",
  { userId: "vip-user", message: "重要通知" },
  { priority: 1 },
);

await reminderQueue.add(
  "send-reminder",
  { userId: "normal-user", message: "普通通知" },
  { priority: 100 },
);

几个容易误解的规则:

因此,如果队列要统一使用优先级,最好所有任务都明确设置非零优先级。不要混用“有优先级”和“无优先级”任务后再假设 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 章

实用技巧与最佳实践

本章小结

delay 控制任务最早何时可执行,priority 改变不同等级任务的顺序,lifo 让新任务先于旧任务。选择排序策略时,应同时考虑公平性、积压、性能和业务是否允许旧任务被推迟。

官方文档