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

 更新时间:2025年07月23日 09:33:47   作者:半部论语  
在电商、外卖、票务等业务中,下单后若 30 分钟未支付则自动取消是一道经典需求,实现方式既要保证 实时性,又要在 高并发 下保持 低成本、高可靠,本文基于 Spring Boot,给出 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-message", 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 或愿意引入 MQ。

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

1. 思路

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

2. Redis 配置

# redis.conf
notify-keyspace-events Ex

或 CLI:

CONFIG SET notify-keyspace-events Ex

3. 代码实现

3.1 订单创建时写 Redis

@Service
public class 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
  • 可靠性:Redis 重启会丢失未过期 key,需 兜底定时任务(方案 1)双保险。

5. 优缺点

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

6. 适用场景

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

六、3 种方案对比与选型

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

建议

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

七、灰度 & 监控

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

八、小结

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

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

以上就是SpringBoot订单超时自动取消的三种主流实现方案的详细内容,更多关于SpringBoot订单超时自动取消的资料请关注脚本之家其它相关文章!

相关文章

  • Can''t use Subversion command line client:svn 报错处理

    Can''t use Subversion command line client:svn 报错处理

    这篇文章主要介绍了Can't use Subversion command line client:svn 报错处理的相关资料,需要的朋友可以参考下
    2016-09-09
  • maven安装配置的实现步骤

    maven安装配置的实现步骤

    本文主要介绍了maven安装配置的实现步骤,包括下载和安装Maven,配置Maven的环境变量,以及创建Maven项目,具有一定的参考价值,感兴趣的可以了解一下
    2023-09-09
  • Java校验银行卡是否正确的核心代码

    Java校验银行卡是否正确的核心代码

    这篇文章主要介绍了Java校验银行卡是否正确的核心代码,需要的朋友可以参考下
    2017-01-01
  • java中generic实例详解

    java中generic实例详解

    这篇文章主要介绍了java中generic实例详解,具有一定参考价值,需要的朋友可以了解下。
    2017-11-11
  • Spring Boot 整合 MyBatis 连接数据库及常见问题

    Spring Boot 整合 MyBatis 连接数据库及常见问题

    MyBatis 是一个优秀的持久层框架,支持定制化 SQL、存储过程以及高级映射,下面详细介绍如何在 Spring Boot 项目中整合 MyBatis 并连接数据库,感兴趣的朋友一起看看吧
    2025-03-03
  • Java编程获取文本框的内容实例解析

    Java编程获取文本框的内容实例解析

    这篇文章主要介绍了Java编程获取文本框的值实例解析,将输入的值保存在一个指定的 txt文件之中,具有一定的参考价值,需要的朋友可以了解。
    2017-09-09
  • SpringBoot项目如何添加2FA双因素身份认证

    SpringBoot项目如何添加2FA双因素身份认证

    双因素身份验证2FA是一种安全系统,要求用户提供两种不同的身份验证方式才能访问某个系统或服务,国内普遍做短信验证码这种的用的比较少,不过在国外的网站中使用双因素身份验证的还是很多的,这篇文章主要介绍了SpringBoot项目如何添加2FA双因素身份认证,需要的朋友参考下
    2024-04-04
  • 阿里SpringBoot应用自动化部署实现IDEA版Jenkins

    阿里SpringBoot应用自动化部署实现IDEA版Jenkins

    这篇文章主要为大家介绍了阿里SpringBoot应用自动化部署实现IDEA版Jenkins过程详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • java -jar后台启动的四种方式小结

    java -jar后台启动的四种方式小结

    这篇文章主要介绍了java -jar后台启动的四种方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-09-09
  • Java中JWT令牌实现登录验证

    Java中JWT令牌实现登录验证

    本文主要介绍了JWT令牌在Java中实现登录验证的方法,JWT是一种自我包含的、无状态的认证机制,可以用来在客户端和服务器之间传递安全可靠的信息,感兴趣的可以了解一下
    2024-12-12

最新评论