下单后 30 分钟未支付要自动取消并释放库存。最初的做法是每分钟一个定时任务扫描 WHERE status='pending' AND created_at < now() - 30 分钟。订单量上来后:扫描越来越慢;取消时间最多晚 1 分钟;多实例部署时定时任务同时执行,重复取消、重复回补库存。
约束
取消时间误差在秒级即可。
绝不能取消已支付的订单(支付回调和超时取消可能同时发生)。
库存回补只能做一次。
已有 RabbitMQ。
方案设计
sequenceDiagram
participant O as 订单服务
participant W as 等待队列(TTL=30 分钟,无消费者)
participant D as 死信交换机
participant C as 取消消费者
participant DB as 数据库
O->>DB: 创建订单(pending)+ outbox 消息(同一事务)
O->>W: 发布 {order_id}
Note over W: 30 分钟后消息过期
W->>D: 转为死信
D->>C: 投递到处理队列
C->>DB: UPDATE ... SET status='cancelled' WHERE id=? AND status='pending'
alt 影响 1 行
C->>DB: 同一事务回补库存
else 影响 0 行(已支付 / 已取消)
C-->>C: 什么都不做,直接 ack
end
延迟消息:TTL + 死信交换机。一个等待队列只放同一种延迟时间(30 分钟)。
幂等取消:用条件更新 WHERE status='pending',和支付回调的 WHERE status='pending' 天然互斥,只有一个会成功;库存回补放在同一事务里,只有取消成功才回补。