Redisson 延迟队列实现订单支付超时自动取消(源码 + 原理全解)

在付款情景中,订单支付超时自动取消 是核心基础功能。本文将结合实战源码,深度讲解基于 Redisson 实现的 Redis 延迟队列方案,解析代码逻辑、核心原理,并解答一个高频疑问:为什么这个方案不能做成监听器模式?

一、业务背景

用户下单后,若 30 分钟内未完成支付,系统需要自动关闭订单、释放库存 。为了保证数据安全,必须满足:订单事务提交成功后,再创建超时任务(避免订单保存失败,却执行了取消任务的 BUG)。

本方案采用:Spring 事务同步 + Redisson 延迟队列 实现,轻量、可靠、开箱即用。


二、核心组件介绍

整个方案由 4 个核心部分组成,分工明确:

  1. 任务实体 :OrderPaymentTimeoutTask 存储订单 ID、订单号、超时时间(仅传递数据,无业务逻辑);
  2. 调度器 :scheduleAfterCommit 事务提交后,将任务加入 Redis 延迟队列;
  3. Redis 队列 :分为延迟队列 (计时等待)+阻塞队列(超时后存放待执行任务);
  4. 消费者 :OrderPaymentTimeoutConsumer 后台线程监听阻塞队列,执行取消订单逻辑。

三、核心源码详解

1. 任务实体(数据载体)

复制代码
public record OrderPaymentTimeoutTask(
        Long orderId,        // 订单ID
        String orderNo,      // 订单编号
        LocalDateTime payExpireTime  // 支付超时时间
) {}
  • 作用:仅存储超时任务所需的核心数据;
  • 执行逻辑:消费者拿到订单 ID 后,固定执行取消未支付订单。

2. 调度器(事务安全入队)

复制代码
public void scheduleAfterCommit(OrderDO order) {
    // 1. 参数校验 + 功能开关判断
    if (order == null || order.getId() == null || order.getPayExpireTime() == null) return;
    if (!properties.isTimeoutDelayQueueEnabled()) return;

    OrderPaymentTimeoutTask task = new OrderPaymentTimeoutTask(order.getId(), order.getOrderNo(), order.getPayExpireTime());

    // 2. 核心:无事务直接调度,有事务则等待提交后调度
    if (!TransactionSynchronizationManager.isActualTransactionActive()) {
        schedule(task, calculateDelay(order.getPayExpireTime()));
        return;
    }

    // 3. 事务提交回调(Spring 回调机制)
    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
        @Override
        public void afterCommit() {
            schedule(task, calculateDelay(order.getPayExpireTime()));
        }
    });
}

核心亮点:

  • 利用 Spring 事务同步器,事务提交成功后才创建任务,杜绝数据不一致;
  • afterCommit() 是典型的回调机制:事务完成后,自动调用我们预设的逻辑。

3. 队列入队逻辑(序列化 + Redis 存储)

复制代码
public void schedule(OrderPaymentTimeoutTask task, Duration delay) {
    // 任务序列化:Redis 无法存储Java对象,必须转为JSON字符串
    String taskJson = JSONObject.toJSONString(task);
    // 加入延迟队列,时间到达后自动移入阻塞队列
    delayedQueue.offer(taskJson, actualDelay.toMillis(), TimeUnit.MILLISECONDS);
}

关键知识点:

  • 序列化:Redis 只支持字符串 / 字节数组,对象必须转 JSON;
  • 队列机制:延迟队列计时 → 时间到 → 任务自动移入阻塞队列。

4. 消费者(后台线程执行取消逻辑)

复制代码
@PostConstruct
public void start() {
    // 单线程后台线程,守护线程(服务关闭自动销毁)
    executorService = Executors.newSingleThreadExecutor(r -> {
        Thread thread = new Thread(r, "order-payment-timeout-consumer");
        thread.setDaemon(true);
        return thread;
    });
    // 启动循环监听
    executorService.submit(this::consumeLoop);
}

// 核心监听循环
private void consumeLoop() {
    RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue(queueName);
    while (running) {
        // 阻塞获取任务:无任务则休眠,不消耗CPU
        String payload = blockingQueue.take();
        OrderPaymentTimeoutTask task = JSON.parseObject(payload, OrderPaymentTimeoutTask.class);
        // 执行订单取消逻辑
        orderPaymentSupportService.handlePaymentTimeout(task);
    }
}

核心逻辑:

  • 项目启动自动创建后台线程;
  • blockingQueue.take() 阻塞等待任务,有任务立即执行;
  • 单线程保证任务顺序执行,避免并发问题。

四、高频灵魂拷问:为什么消费者不做成监听器?

很多开发者会疑惑:既然是监听队列,为什么不用注解式监听器(如 @RabbitListener),反而要写死循环?

1. 底层限制:Redis 不支持监听器模式

  • 专业消息队列(RabbitMQ/RocketMQ) :支持主动推送消息,有成熟的监听器机制,消息到达后自动回调;
  • Redis :仅作为缓存 / 存储,无消息推送能力,无法主动通知服务。

2. 现有方案是 Redis 队列的标准实现

Redis 阻塞队列的唯一使用方式:开启线程主动调用 take() 阻塞等待。

  • 无任务时,线程休眠,CPU 占用极低;
  • 有任务时,立即唤醒执行,性能与监听器一致。

3. 总结

不是不想做监听器,是 Redis 底层不支持! 当前 while 循环 + take() 阻塞监听,是 Redis 延迟队列官方、标准、最优的写法。


五、核心知识点总结

  1. 回调机制 :afterCommit 事务提交回调,保证任务与事务一致性;
  2. 序列化:Redis 不支持 Java 对象,必须转为 JSON 字符串存储;
  3. 双队列设计 :
    • 延迟队列:负责计时等待;
    • 阻塞队列:时间到达后存放任务,供消费者消费;
  4. 监听模式:Redis 无推送能力,必须用后台线程阻塞监听,这是标准实现;
  5. 专用性 :当前代码为订单超时取消定制,仅支持这一种任务,不通用。

六、适用场景

  • 中小型电商系统;
  • 轻量级延迟任务(订单超时、验证码过期等);
  • 不想部署重型 MQ,追求极简架构。

本方案无需独立部署中间件,基于 Redis 即可实现可靠的延迟任务,是中小项目的最优选择!

相关推荐
ly76891 小时前
PostgreSQL 执行计划深读:从 EXPLAIN 输出到代价模型驱动的 SQL 改写
数据库·sql·postgresql·explain·执行计划·代价模型
Python自动化直播2 小时前
自动化笔记:素材管理、命名、轮换的几条土办法
linux·运维·数据库
xiaomuzuirenzhen2 小时前
2027校招ERP实施顾问:大数据管理与应用专业如何用SQL、BI证明业务能力
大数据·数据库
底层玩家老张2 小时前
从副本集到分片:百亿级文档数据的KES架构边界
数据库·分布式架构·分片·架构选型·文档数据库
ss2732 小时前
AI时代首先数据库-PostgreSQL实战笔记 | 16 PostgreSQL子查询与嵌套查询(下)
数据库·笔记·postgresql
川石课堂软件测试2 小时前
涨薪技术|0到1学会性能测试第8课-Analysis分析图
服务器·数据库·python·sql·jmeter·mysql·容器
SelectDB技术团队2 小时前
云数仓成本结构拆解:五层计费模型与 Apache Doris 托管形态对照
大数据·数据库·elk·全文检索·snowflake
夕除3 小时前
redis--ziplist
redis
我命由我123453 小时前
SQL 检测脚本
java·数据库·sql·功能测试·单元测试·压力测试·可用性测试
Zenova EdgeOS3 小时前
工业网关数据全链路加密实战:从传输、静态数据到密钥轮
数据库·oracle·边缘计算·工业网关