极高 进阶
订单超时取消方案#
一句话答案#
推荐延迟 MQ(RocketMQ 延迟消息)作主力 + DB 定时扫描兜底,权衡精度、可靠性和复杂度。
核心要点
| 方案 | 精度 | 可靠性 | 复杂度 |
|---|---|---|---|
| RocketMQ延迟消息 | 固定级别 | 高 | 低(推荐) |
| Redis ZSET+轮询 | 秒级 | 中 | 中 |
| 时间轮 | 高 | 低(内存) | 中 |
| 定时扫描DB | 分钟级 | 高 | 低 |
推荐: 延迟MQ作主力 + 定时扫描兜底
面试回答(2分钟版)
订单超时取消我推荐”主力加兜底”的双保险方案。主力用延迟消息队列,比如RocketMQ支持18个延迟级别,下单时发一条30分钟延迟消息,到时间后消费者检查订单状态,未支付就执行取消。兜底用DB定时扫描,每分钟跑一次查询超时未付订单,捞出延迟消息丢失的漏网之鱼。为什么不单用一种?单靠延迟MQ有消息丢失风险,单靠定时扫描精度只有分钟级且对DB有压力。取消订单不只是改状态,还要释放库存、归还优惠券、退还积分、通知用户,这些操作建议通过事件机制解耦。如果用Redis方案,可以把订单ID放进ZSET,score是过期时间戳,轮询取出到期订单,精度能到秒级但可靠性不如MQ。实际生产中延迟MQ加DB扫描的组合是最稳妥的选择。
追问与易错
追问方向:
- “延迟 MQ 不够精确怎么办?”→ RocketMQ 延迟级别固定(如 1s/5s/30s/1m/…30m),无法精确到任意时间;可以选最近的级别(如 30 分钟超时用 30m 级别),或者用 RabbitMQ 的 TTL+死信队列实现任意延迟
- “兜底扫描频率怎么设?”→ 通常每分钟扫描一次,查询条件为 status=未支付 AND create_time < NOW()-超时时间,加索引保证查询效率;频率太高 DB 压力大,太低漏单时间长,1 分钟是平衡点
- “取消订单需要做哪些操作?”→ 不只是改订单状态,还需要:释放库存(Redis+DB)、归还优惠券、退还积分、取消支付预授权、通知用户;建议通过发布领域事件让各模块订阅处理,实现解耦
易错点:
- ❌ 用一种方案就够——推荐主力+兜底双保险
- ❌ 定时扫描不能用——作为兜底完全可以