标准答案
- 用优先队列或数据库索引按 availableAt 取到期任务。
- 领取任务时原子地从 pending 改为 processing,并设置租约,避免消费者崩溃后永久丢失。
- 用唯一键或幂等记录保护重复投递,取消和过期任务不能继续产生副作用。
题目解析
延迟任务不仅要按 availableAt 找到期记录,还要处理领取、租约、取消、失败和进程崩溃。单机定时器无法在重启后恢复任务,也无法让多实例安全竞争。
领取必须是原子状态转换,例如 pending 到 processing,并设置 leaseUntil。租约过期后才能被其他消费者接管,完成或失败要保存结果和重试时间。
重复投递是正常故障路径。唯一幂等键、业务状态条件和外部副作用回查要共同保证重复任务不会重复扣款、发货或发布事件。
代码示例
持久化任务至少需要这些字段:
TypeScript
type Task = { id: string; dedupeKey: string; availableAt: number; status: "pending" | "processing" | "done"; leaseUntil?: number }常见误区
- 误区:只在内存定时器中保存任务。改正:把任务和到期时间持久化,进程重启后能够扫描恢复。
- 误区:领取任务后没有租约。改正:使用带过期时间的 processing 状态,允许崩溃任务被安全接管。
- 误区:重复投递直接执行副作用。改正:用唯一幂等键和业务条件更新保护最终写入。