开发者

SpringBoot订单超时自动取消的三种主流实现方案

目录
  • 引言
  • 一、需求拆解
  • 二、方案总览
  • 三、方案 1:定时任务(@Scheduled)
    • 1. 思路
    • 2. 代码实现
    • 3. 优化技巧
    • 4. 优缺点
    • 5. 适用场景
  • 四、方案 2:RabbitMQ 延迟队列
    • 1. 思路
    • 2. 架构图
    • 3. 代码实现
      • 3.1 声明交换机 & 队列
      • 3.2 发送延迟消息
      • 3.3 消费并取消
    • 4. 优缺点
      • 5. 适用场景
      • 五、方案 3:Redis Keyspace 过期事件
        • 1. 思路
          • 2. Redis 配置
            • 3. 代码实现
              • 3.1 订单创建时写 Redis
              • 3.2 监听过期事件
            • 4. 幂等 & 可靠性
              • 5. 优缺点
                • 6. 适用场景
                • 六、3 种方案对比与选型
                  • 七、灰度 & 监控
                    • 八、小结

                      引言

                      在电商、外卖、票务等业务中,“下单后若 30 分钟未支付则自动取消”是一道经典需求。实现方式既要保证 实时性,又要在 高并发 下保持 低成本、高可靠

                      本文基于 Spring Boot,给出 3 种生产级落地方案,并附完整代码与选型对比,方便快速决策。

                      一、需求拆解

                      功能点约束
                      触发条件创建时间 + 30 min 仍未支付
                      实时性秒级(理想) / 分钟级(可接受)
                      幂等重复取消需幂等
                      高并发峰值 10 w+/日
                      数据一致性不能漏单、不能错单

                      二、方案总览

                      方案核心机制实时性额外组件代码复杂度
                      ① 定时任务@Scheduled + DB 扫描分钟级★☆☆
                      ② 延迟队列RabbitMQ TTL + DLX秒级RabbitMQ★★☆
                      ③ Redis 过期事件Key TTL + Keyspace Notify秒级Redis★★☆

                      三、方案 1:定时任务(@Scheduled)

                      1. 思路

                      周期性扫描订单表,把“创建时间 + 30 min < 当前时间”且状态为 PENDING 的订单置为 CANCELLED

                      2. 代码实现

                      @EnableScheduling
                      @Component
                      @RequiredArgsConstructor
                      public class OrderCancelSchedule {
                      
                          private final OrderService orderService;
                      
                          /** 每 30s 跑一次,可根据数据量调整 */
                          @Scheduled(fixedDelay = 30_000)
                          public void cancelUnpaidOrders() {
                              LocalDateTime expirePoint = LocalDateTime.now().minusMinutes(30);
                              List<Long> ids = orderService.findUnpaidBefore(expirePoint);
                              if (!ids.isEmpty()) {
                                  int affected = orderService.BATchCancel(ids);
                                  log.info("自动取消订单 {} 条", affected);
                              }
                          }
                      }
                      

                      3. 优化技巧

                      • 分页 + 索引
                      CREATE INDEX idx_order_status_created ON t_order(status, created_time);
                      
                      • 分片扫描:按 ID 或时间分片,避免大表锁。
                      • 单机多线程@Async("cancelExecutor") + 线程池。

                      4. 优缺点

                      • ✅ 零依赖、实现快
                      • ❌ 数据量大时 DB 压力大;实时性受轮询间隔限制

                      5. 适用场景

                      日订单 < 1 w,或作为兜底方案。

                      四、方案 2:RabbitMQ 延迟队列

                      1. 思路

                      订单创建后发送一条 30 min TTL 的消息;到期自动路由到消费队列,消费者检查订单状态并取消。

                      2. 架构图

                      Producer ──> Delay Exchange (x-delayed-message) ──> 30min TTL ──> Cancel Queue ──> Consumer
                      

                      3. 代码实现

                      3.1 声明交换机 & 队列

                      @Configuration
                      public class RabbitDelayConfig {
                      
                          @Bean
                          public CustomExchange delayExchange() {
                              Map<String, Object> args = Map.of("x-delayed-type", "direct");
                              return new CustomExchange("order.delay", "x-delayed-mjLLTseCnbessage", true, false, args);
                          }
                      
                          @Bean
                          public Queue cancelQueue() {
                              return QueueBuilder.durable("order.cancel.queue").build();
                          }
                      
                          @Bean
                          public Binding binding() {
                              return BindingBuilder.bind(cancelQueue()).to(delayExchange()).with("order.cancel").noargs();
                          }
                      }
                      

                      3.2 发送延迟消息

                      @Service
                      @RequiredArgsConstructor
                      public class OrderPublisher {
                          private final RabbitTemplate rabbitTemplate;
                      
                          public void createOrder(Order order) {
                              // 1. 落库
                              orderMapper.insert(order);
                              // 2. 发送延迟消息
                              rabbitTemplate.convertAndSend(
                                  "order.delay",
                                  "order.cancel",
                                  order.getId(),
                                  msg -> {
                                      msg.getMessageProperties().setDelay(30 * 60 * 1000); // 30 min
                                      return msg;
                                  }
                              );
                          }
                      }
                      

                      3.3 消费并取消

                      @Component
                      @RabbitListener(queues = "order.cancel.queue")
                      public class CancelConsumer {
                          private final OrderService orderService;
                      
                          @RabbitHandler
                          public void handle(Long orderId) {
                              Order order = orderService.find(orderId);
                              if (order != null && order.getStatus() == OrderStatus.PENDING) {
                                  orderService.cancel(orderId);
                              }
                          }
                      }
                      

                      4. 优缺点

                      • ✅ 实时性好(秒级);支持分布式;消息持久化
                      • ❌ 需要 RabbitMQ;链路更长

                      5. 适用场景

                      中高并发,需秒级取消,已用 MQ 或javascript愿意引入 MQ。

                      五、方案 3:Redis Keyspace 过期事件

                      1. 思路

                      order:{id} 作为 key,30 min TTL;Redis 键过期时推送事件js;应用监听后取消订单。

                      2. Redis 配置

                      # redis.conf
                      notify-keyspace-events Ex
                      

                      或 CLI:

                      CONFIG SET notify-keyspace-events Ex
                      

                      3. 代码实现

                      3.1 订单创建时写 Redis

                      @Service
                      public classwww.devze.com OrderService {
                          private final StringRedisTemplate redisTemplate;
                      
                          public void createOrder(Order order) {
                              orderMapper.insert(order);
                              // value 随意,这里用 id
                              redisTemplate.opsForValue()
                                .set("order:" + order.getId(),
                                     String.valueOf(order.getId()),
                                     Duration.ofMinutes(30));
                          }
                      }
                      

                      3.2 监听过期事件

                      @Configuration
                      public class RedisListenerConfig {
                      
                          @Bean
                          public RedisMessageListenerContainer container(RedisConnectionFactory cf) {
                              RedisMessageListenerContainer container = new RedisMessageListenerContainer();
                              container.setConnectionFactory(cf);
                              container.addMessageListener(
                                  (message, pattern) -> {
                                      String key = message.toString();
                                      if (key.startsWith("order:")) {
                                          String orderId = key.substring(6);
                                          // 幂等取消
                                          orderService.cancelIfUnpaid(Long.valueOf(orderId));
                                      }
                                  },
                                  new PatternTopic("__keyevent@*__:expired")
                              );
                              return container;
                          }
                      }
                      

                      4. 幂等 & 可靠性

                      • 幂等:取消 SQL 加状态条件 WHERE status = PENDING
                      • 可靠性php:Redis 重启会丢失未过期 key,需 兜底定时任务(方案 1)双保险。

                      5. 优缺点

                      • ✅ 实时性高,组件少
                      • ❌ Redis 重启可能丢事件;需处理幂等

                      6. 适用场景

                      已用 Redis,订单量中等,能接受极低概率漏单。

                      六、3 种方案对比与选型

                      维度定时任务RabbitMQ 延迟队列Redis 过期事件
                      实时性分钟级秒级秒级
                      吞吐量
                      额外组件RabbitMQRedis
                      可靠性中(需兜底)
                      实现复杂度★☆☆★★☆★★☆
                      推荐场景小流量、兜底高并发、已用 MQ已用 Redis、中等并发

                      建议

                      1. 小项目 → 定时任务即可;
                      2. 大流量 → 延迟队列;
                      3. 已用 Redis → 过期事件 + 定时任务兜底双保险。

                      七、灰度 & 监控

                      • 灰度发布:按用户尾号或城市分批切换方案。
                      • 监控指标
                        • 取消成功率
                        • MQ 消息积压
                        • Redis 过期 QPS
                        • 定时任务扫描耗时

                      八、小结

                      一句话总结定时任务 简单但慢;延迟队列 实时但重;Redis 过期 轻量但需兜底。

                      在实际落地中,可以 并行运行 两种方案(如延迟队列 + 兜底定时任务),通过配置开关灵活切换,确保业务永远在线。祝你的订单永不超卖!

                      以上就是SpringBoot订单超时自动取消的三种主流实现方案的详细内容,更多关于SpringBoot订单超时自动取消的资料请关注编程客栈(www.devze.com)其它相关文章!

                      0

                      上一篇:

                      下一篇:

                      精彩评论

                      暂无评论...
                      验证码 换一张
                      取 消

                      最新开发

                      开发排行榜