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

相关推荐
HackTwoHub4 小时前
AI大模型网关存在SQL注入、附 POC 复现、影响版本LiteLLM 1.81.16~1.83.7(CVE-2026-42208)
数据库·人工智能·sql·网络安全·系统安全·网络攻击模型·安全架构
l1t4 小时前
DeepSeek总结的DuckLake构建基于 SQL 原生表格式的下一代数据湖仓
数据库·sql
KmSH8umpK4 小时前
Redis分布式锁从原生手写到Redisson高阶落地,附线上死锁复盘优化方案进阶第八篇
数据库·redis·分布式
TDengine (老段)5 小时前
从施工监测到运营预警,桥科院用 TDengine 提升桥梁数据管理能力
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
callJJ5 小时前
Spring Data Redis 两种编程模型详解:同步 vs 响应式
java·spring boot·redis·python·spring
S1998_1997111609•X6 小时前
论mysql国盾shell-sfa犯罪行为集团下的分项工程及反向注入原理尐深度纳米算法下的鐌檵鄐鉎行为
网络·数据库·网络协议·百度·开闭原则
KmSH8umpK7 小时前
Redis分布式锁从原生手写到Redisson高阶落地,附线上死锁复盘优化方案进阶第七篇
数据库·redis·分布式
0xDevNull7 小时前
Linux 中 Nginx 代理 Redis 的详细教程
redis·后端
BU摆烂会噶8 小时前
【LangGraph】持久化实现的三大能力——时间旅行
数据库·人工智能·python·postgresql·langchain
l1t8 小时前
DeepSeek总结的DuckLake 入门
数据库