Redisson延迟队列实现订单关闭的操作方法

 更新时间:2026年03月05日 09:17:44   作者:皮卡丘学了没  
Redisson的RDelayedQueue是实现订单到期关闭的利器,它比定时任务更精准、比Redis过期监听更可靠,下面一步步给大家介绍Redisson延迟队列实现订单关闭功能,感兴趣的朋友一起看看吧

Redisson的RDelayedQueue是实现订单到期关闭的利器,它比定时任务更精准、比Redis过期监听更可靠。其核心原理是利用Redis的**有序集合(Sorted Set)发布/订阅(Pub/Sub)**功能,在分布式环境下可靠地执行延迟任务。

接下来,我们结合Java代码,一步步来看如何实现。

🚀 生产端:订单创建时,埋下“定时炸弹”

在用户下单成功后,我们需要做的不是立即启动一个计时器,而是将这笔订单的ID作为一条“延迟消息”发送出去。

import org.redisson.api.RBlockingQueue;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Service
public class OrderService {
    @Resource
    private RedissonClient redissonClient;
    // 队列的名称,可以理解为用于存放待关闭订单的“信箱”
    private static final String ORDER_QUEUE_KEY = "order-close-queue";
    public void createOrder(Order order) {
        // 1. 保存订单到数据库 (省略具体逻辑)
        saveToDB(order);
        log.info("订单 [{}] 创建成功,等待支付...", order.getId());
        // 2. 将订单ID放入延迟队列,设置30分钟后“爆炸”
        try {
            // 获取一个阻塞队列,这是消费者最终要监听的目标队列
            RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue(ORDER_QUEUE_KEY);
            // 基于阻塞队列,创建一个延迟队列
            RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
            // 将订单ID放入延迟队列,延迟30分钟
            delayedQueue.offer(order.getId().toString(), 30, TimeUnit.MINUTES);
            log.info("订单 [{}] 已放入延迟队列,30分钟后将自动关闭", order.getId());
        } catch (Exception e) {
            log.error("放入延迟队列失败", e);
            // 这里可以考虑补偿机制,比如记录日志后由定时任务兜底
        }
    }
}

核心逻辑:生产者调用delayedQueue.offer(),将任务(订单ID)和延迟时间(30分钟)告诉Redisson。Redisson客户端会把这个任务连同计算好的执行时间戳,一起存到Redis的一个**有序集合(ZSet)**中,并以时间戳作为排序的分数(score)。

🎧 消费端:时刻待命,准时“拆弹”

我们需要一个后台任务一直监听,一旦有订单到期,立刻执行关闭操作。

import org.redisson.api.RBlockingQueue;
import org.redisson.api.RedissonClient;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class OrderCloseListener implements CommandLineRunner {
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private OrderService orderService; // 注入你的订单Service
    private static final String ORDER_QUEUE_KEY = "order-close-queue";
    @Override
    public void run(String... args) throws Exception {
        // 程序启动后,在一个独立的线程中监听队列
        new Thread(() -> {
            log.info("订单关闭监听线程已启动,等待到期订单...");
            // 获取与生产者相同的阻塞队列
            RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue(ORDER_QUEUE_KEY);
            // 注意:这里必须调用一次getDelayedQueue,目的是在消费端也初始化相关的监听器
            // 虽然不调用也能工作,但官方推荐调用以保证可靠性 
            redissonClient.getDelayedQueue(blockingQueue); 
            while (true) {
                try {
                    // 阻塞等待,直到有订单到期。take()方法会一直阻塞直到拿到数据
                    String orderId = blockingQueue.take();
                    log.info("收到到期订单ID:{},开始执行关闭操作", orderId);
                    // 执行真正的关单业务逻辑
                    orderService.closeExpiredOrder(Long.parseLong(orderId));
                } catch (InterruptedException e) {
                    log.error("监听线程被中断", e);
                    Thread.currentThread().interrupt();
                    break; // 线程中断时退出循环
                } catch (Exception e) {
                    log.error("处理到期订单时发生错误", e);
                    // 防止单个消息处理失败导致循环中断,继续监听下一个
                }
            }
        }, "OrderClose-Listener").start();
    }
}

核心逻辑:消费者通过blockingQueue.take()阻塞地从Redis的**目标队列(List)**中获取消息。当一个任务在ZSet中的时间戳小于当前时间,Redisson的后台线程就会自动把它从ZSet移动到目标List中。这时,take()方法就会立即返回,拿到订单ID,执行关闭逻辑。

🔍 探秘Redisson的“内部时钟”机制

你可能会好奇,Redisson是如何精准地将到期任务从ZSet移到List的?这背后有一个巧妙的设计:

Redisson为每个延迟队列启动了一个后台轮询线程(基于Netty的时间轮实现)。这个线程会:

  1. 定期查询ZSet中分数最小(即最早到期)的任务。
  2. 如果该任务的到期时间戳小于当前时间,就把这个任务以及所有其他到期的任务,从ZSet和另一个辅助的List中移除,并推入到消费者正在监听的目标List中。
  3. 为了提高效率,当有新的、更早到期的任务加入时,Redisson会通过**发布/订阅(Pub/Sub)**功能发送一个通知,唤醒轮询线程立即工作,而不是等到下一个轮询周期。

所以,整个过程就像有一个精准的“闹钟”在帮你管理这些任务。

💡 实战要点与进阶思考

  1. 消息可靠性:所有任务数据都存储在Redis中,即使你的应用服务重启,已经提交但未到期的任务也不会丢失。Redis的持久化(RDB/AOF)机制为数据提供了最终保障。
  2. 分布式支持:生产者和消费者可以是完全不同的应用实例,只要它们连接同一个Redis、并使用相同的队列名称(如order-close-queue)即可。多个消费者实例可以同时take同一个队列,实现任务的负载均衡。
  3. 时间精度:Redisson的默认轮询间隔约为5秒,这意味着任务的触发时间可能会有最多5秒的误差。如果业务对时间精度要求极高,可以通过配置config.setScanInterval(2000)来缩短轮询间隔(单位为毫秒),但会略微增加Redis的压力。
  4. 业务兜底:作为一种最佳实践,即使有了延迟队列,也建议配合一个低频的定时任务(如每小时执行一次)作为最后的检查,去扫描那些极少数可能因为各种意外(如Redis故障)而未被关闭的订单,确保业务逻辑的最终一致性。

到此这篇关于Redisson延迟队列实现订单关闭的操作方法的文章就介绍到这了,更多相关redisson延迟队列订单关闭内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Windows下将JAVA jar注册成windows服务的方法

    Windows下将JAVA jar注册成windows服务的方法

    这篇文章主要介绍了Windows下将JAVA jar注册成windows服务的方法,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-07-07
  • Java List对象集合中 如何根据集合中对象某几个属性组合去重

    Java List对象集合中 如何根据集合中对象某几个属性组合去重

    本文介绍Java中基于对象属性去重的三种方法,各方法在保持顺序和实现复杂度上有所不同,适用于不同场景,本文结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2025-09-09
  • java实现音频文件播放功能

    java实现音频文件播放功能

    这篇文章主要为大家详细介绍了java实现音频文件播放功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-12-12
  • 使用@SpringBootTest注解进行单元测试

    使用@SpringBootTest注解进行单元测试

    这篇文章主要介绍了使用@SpringBootTest注解进行单元测试,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • SpringBoot业务逻辑异常的处理方法介绍

    SpringBoot业务逻辑异常的处理方法介绍

    本篇文章为大家展示了如何在SpringBoot中统一处理逻辑异常,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获
    2022-09-09
  • MyBatis还是JPA?终于有答案了

    MyBatis还是JPA?终于有答案了

    这篇文章主要介绍了MyBatis还是JPA,中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-10-10
  • 为什么 Java 8 中不需要 StringBuilder 拼接字符串

    为什么 Java 8 中不需要 StringBuilder 拼接字符串

    java8中,编辑器对“+”进行了优化,默认使用StringBuilder进行拼接,所以不用显示的使用StringBuilder了,直接用“+”就可以了。下面我们来详细了解一下
    2019-05-05
  • 浅谈Spring中Bean的作用域、生命周期

    浅谈Spring中Bean的作用域、生命周期

    这篇文章主要介绍了浅谈Spring中Bean的作用域、生命周期,具有一定借鉴价值,需要的朋友可以参考下
    2018-01-01
  • grade构建阅读spring源码环境 Idea2020.3的过程

    grade构建阅读spring源码环境 Idea2020.3的过程

    这篇文章主要介绍了grade构建阅读spring源码环境 Idea2020.3,本文分步骤通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-10-10
  • Java volatile关键字原理剖析与实例讲解

    Java volatile关键字原理剖析与实例讲解

    volatile是Java提供的一种轻量级的同步机制,Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,本文将详细为大家总结Java volatile关键字,通过详细的代码示例给大家介绍的非常详细,需要的朋友可以参考下
    2023-07-07

最新评论