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 即可实现可靠的延迟任务,是中小项目的最优选择!

相关推荐
数厘2 小时前
2.13 sql数据更新(UPDATE)
数据库·sql·oracle
一江寒逸2 小时前
零基础从入门到精通MongoDB(附加篇):面试八股文全集
数据库·mongodb·面试
星晨雪海2 小时前
Redis 分布式 ID 生成器
数据库·redis·分布式
有味道的男人2 小时前
抖音关键词搜索,视频详情api
linux·数据库·音视频
丁丁点灯o2 小时前
Oracle中金额数字转换为大写汉字
数据库·oracle
fly spider2 小时前
MySQL之Buffer Pool
数据库·mysql
程序员老邢2 小时前
【技术底稿 13】内网 Milvus 2.3.0 向量数据库全流程部署(商助慧 AI 底座,Attu 可视化)
java·数据库·人工智能·ai·语言模型·milvus
XDHCOM3 小时前
ORA-38456: 属性集状态不一致,Oracle报错修复对比,远程处理方案选择
数据库·oracle
羊小蜜.3 小时前
Mysql 14: 存储引擎——架构、引擎对比与锁机制
数据库·mysql·架构