Skip to content

延迟任务与到期处理 ​

延迟任务在某个时间之后获得执行资格。例如订单创建 30 分钟后,系统检查它是否仍然待支付。任务的计划时间决定何时开始检查,当前业务状态决定动作是否成立。

计划时间与实际开始时间 ​

建议区分三个时间:

字段含义
scheduled_at原始计划时间,用来衡量业务时效
available_at下一次允许领取的时间,重试时可以后移
started_at本次尝试真正开始处理的时间

后移重试时间时保留原始计划时间,否则延迟指标会随着每次重试被“刷新”。是否允许提前执行、可以晚多久、晚到后是否仍然有价值,都应由业务规则决定。

等待机制 ​

方式等待在哪里主要边界
进程内定时器内存进程重启后需要从持久化记录重建
数据库到期扫描带执行时间的任务表索引、扫描间隔和领取竞争
按时间排序的集合按到期时间排序的数据结构领取与处理中记录需要原子衔接
支持延迟投递的消息系统消息代理最大延迟、精度、持久化和确认规则

Redis 有序集合支持按 score 排序与范围查询,可以用时间戳寻找候选任务;这是底层数据结构能力。若查询后直接删除,再交给 Worker,进程在中间崩溃就会丢失任务,需要额外设计处理中记录与恢复。参见 Redis sorted sets。

消息系统也不一定支持任意长的延迟。以当前 SQS 文档为例,延迟队列的最大延迟是 15 分钟,不能直接用它表达 30 分钟或数月后的所有任务。选型时应核对具体产品的限制,见 SQS delay queues。

改期与取消 ​

预约由 10:00 改到 11:00 后,原提醒任务可能已经投递,不能只依赖删除旧消息。一个可追溯的设计是:

  1. 业务事务更新预约时间并增加版本号。
  2. 同一事务保存新版本对应的提醒任务。
  3. 旧任务执行时读取预约,发现版本不匹配后记录为 skipped。

业务键可以是 appointment_id + reminder_type + version。这样新旧计划各有独立记录,迟到的旧任务也能自行退出。版本号只应在影响该动作的业务变化时推进,避免无关更新使任务失效。

取消同样需要说明生效点:尚未开始的任务可以标记 cancelled;已经运行的任务需要处理器协作检查。邮件已经发送后,再更新取消标记无法撤回邮件。

到期时重新判断条件 ​

关闭订单时应在同一条写入语句里检查待支付状态、期限和相关版本,不能先读取“未支付”,随后无条件更新为“已关闭”。完整的并发路径见订单超时关闭。

对提醒任务,还需要判断消息是否仍有业务价值。例如会议已经结束,迟到的“还有 10 分钟开始”提醒应跳过,而不是为了提高成功率继续补发。

延迟投递与执行租约 ​

延迟投递控制首次可见时间;执行租约控制领取之后何时可以被其他 Worker 再次领取。两者用途不同。

SQS 的可见性超时发生在接收消息之后,超时且未删除的消息可以再次被接收。它的投递语义仍要求消费者处理重复;调整租约长度不能替代业务幂等。

参考资料 ​