电商常见需求:下单后 30 分钟未支付,自动关闭订单并释放库存。实现方式有好几种,各有取舍。
方案一:定时任务扫描
每分钟扫一次 status = 'pending' AND created_at < NOW() - INTERVAL 30 MINUTE。
优点:简单,不用额外组件。缺点:精度差(最多延迟一分钟),订单量大时扫描慢,需要索引。
方案二:Redis 过期监听
下单时写一个 key order:timeout:{id},设 30 分钟过期,监听 Redis 的 __keyevent@0__:expired 事件。
优点:实时性好。缺点:Redis 过期事件不保证准时,可能延迟;重启会丢事件;集群下事件只发到一个节点,需要额外处理。
方案三:延迟队列
用 RabbitMQ 的 TTL + 死信队列,或者 RocketMQ 的延迟消息。下单时发一条 30 分钟延迟消息,到期消费,检查订单状态,未支付则关闭。
优点:可靠,精度高,解耦。缺点:要维护 MQ,复杂度上升。
方案四:时间轮
Netty 的 HashedWheelTimer 或者自己实现,适合单机、量大的场景。分布式下要配合分片。
怎么选
- 小项目、量不大:定时任务够了
- 中等规模、已有 MQ:延迟队列
- 追求实时、能接受最终一致:Redis 过期 + 定时任务兜底
- 大促场景:延迟队列 + 定时任务双保险
不管哪种,关闭订单时都要加状态条件更新,防止并发下重复关闭或已支付被误关。

Comments 3 条评论
定时任务扫全表,量大真的顶不住
Redis 过期事件不保证准时这点我踩过
关单一定要加状态条件更新