看到网上有人列出了十几种方案,只要沾点边的都列了出来,太多的方案只会让人更加迷惑,下面来说下,企业真正在应用的方式。
订单超时未支付自动关闭是电商、票务等系统的核心功能(如 "下单后 30 分钟未支付自动取消,释放库存"),其实现需平衡实时性、可靠性、资源消耗三大核心指标。
方案 1:延迟消息
优先选择支持 "原生延迟消息" 的分布式 MQ(如 RocketMQ、Pulsar),而非基于死信队列模拟(如 RabbitMQ),原因是原生延迟消息的精度更高(毫秒级)、吞吐更大(单队列百万级消息 / 秒)。
例:RocketMQ 的定时消息支持 1s~24h 的延迟级别,且可通过配置扩展更长时间;Pulsar 支持任意时间精度的延迟消息,更灵活。
实现过程
-
订单创建时,向消息队列发送一条 "延迟消息",延迟时间 = 超时时间(如 30 分钟);
-
消息队列在延迟时间到期后,将消息投递到消费队列;
-
消费者收到消息后,检查订单状态(若仍为 "待支付"),执行关闭逻辑。
优缺点
-
优点:
-
可靠性高:消息可持久化,支持重试(失败后重新入队);
-
实时性好:延迟时间精度高;
-
解耦性强:生产端发送消息后无需关心后续处理,适合分布式系统。
-
缺点:
-
实现复杂:需依赖并配置延迟队列;
-
资源消耗高:需维护消息队列集群,针对秒杀场景(超时关单很少)不太有性价比
适用场景
-
订单量大(日均百万级以上),对可靠性和实时性要求高(如秒杀订单、票务订单,超时需立即释放库存);
-
分布式系统(多服务协同,需解耦订单创建与关闭逻辑);
-
技术栈已包含消息队列(如电商大促场景通常已部署 MQ)。
方案 2:定时任务扫描
实现过程
-
用定时任务框架(如 XXL-Job)配置周期性任务(如每 5 分钟执行一次);
-
任务执行时,查询数据库中 "状态 = 待支付" 且"创建时间 + 超时时间 < 当前时间"的订单;
-
批量执行 "关闭订单 + 释放库存" 逻辑。
代码示意(伪代码)
scss
// XXL-Job定时任务
@XxlJob("closeTimeoutOrderJob")
public void closeTimeoutOrder() {
// 计算超时时间(如30分钟前)
LocalDateTimetimeoutTime= LocalDateTime.now().minusMinutes(30);
// 分页查询超时未支付订单
Page<Order> timeoutOrders = orderMapper.selectByStatusAndCreateTime(OrderStatus.PENDING_PAY, timeoutTime, PageRequest.of(0, 1000) );
// 批量关闭
for (Order order : timeoutOrders.getContent()) {
closeOrder(order.getId());
}}
优缺点
-
优点:
-
可靠性高:基于数据库查询,漏处理概率极低;
-
实现简单:无需依赖 Redis/MQ,适合中小团队;
-
支持批量处理:一次任务可处理大量订单,效率高。
-
缺点:
-
实时性差:延迟取决于任务周期(如 5 分钟执行一次,最大延迟 5 分钟);
-
数据库压力大:订单量过亿时,
select * from order where status=1 and create_time < ?
可能全表扫描(需优化索引)。
适用场景
-
订单量小(日均 1 万级以内),对实时性要求低(如普通商品订单,延迟 5 分钟不影响用户体验);
-
技术栈简单,不想引入 Redis/MQ 等中间件,希望用数据库原生能力兜底;
-
可通过索引优化(如
status+create_time
联合索引)降低数据库压力。
方案 2 升级:基于索引优化、分片扫描的方案
与 "定时任务" 类似,但专为超大规模订单表优化:
优化点
-
给订单表加
status+create_time
联合索引(确保查询不扫全表); -
记录上次扫描到的最大订单号,下次扫描可带入条件;
-
使用定时任务框架的分片并行扫描;
-
按 "创建时间分表"(如按天分表),定时任务仅扫描 "可能超时的分表"(如只扫 30 分钟前创建订单所在的分表);
-
用
limit
分页查询,每次处理小批量订单(如 1000 条 / 次),避免数据库压力峰值。
优缺点
-
优点:纯数据库实现,无需依赖中间件;适合超大规模订单表(亿级以上)。
-
缺点:实时性仍取决于任务周期;分表逻辑增加了实现的复杂度。
适用场景
- 订单量极大(日均千万级以上),且已做分库分表(如电商平台订单系统)。
注意点
所有方案都可能因异常(如网络波动、服务宕机)导致漏处理,需配合:
-
消息去重 :用
orderId
作为消息唯一标识,避免重复消费; -
状态校验:处理关闭订单前,必须再次检查订单状态(防止重复关闭);
-
兜底任务:每日凌晨执行一次全量扫描,处理所有 "漏网" 的超时订单;
-
日志监控:记录关闭订单的详细日志(谁、何时、订单号),便于问题追溯。
-
关单遇到支付回调处理 :参考文章:《面试: 当支付回调遇到超时关单如何处理?》
大企业的订单超时关单方案,本质是 " 主方案 + 兜底机制,全链路靠监控和容灾保证稳定性 "。其设计不追求 "单一方案完美",而是通过 "多层协同" 平衡 "实时性、可靠性、资源消耗",最终实现 "即使某一层出问题,整体系统仍能正常运转" 的高可用目标。
往期精彩推荐:
面试官:如何避免重复下单的问题?
为什么淘宝订单号后 6 位始终一样?
面试: 当支付回调遇到超时关单如何处理
更多文章,可关注公众号《码上实战》