Redisson延迟队列实现及源码探究

废话不多说,上延迟队列实现实现代码

kotlin 复制代码
implementation group: 'org.redisson', name: 'redisson-spring-boot-starter', version: '3.25.0'
kotlin 复制代码
@Test
fun contextLoads() {
  val blockingQueue = redisson.getBlockingDeque<String>("TEST-QUEUE")
  val delayQueue = redisson.getDelayedQueue(blockingQueue)
  Thread {
    while (true) {
      try {
        // 必须使用 blockingQueue 的 poll() 方法!
        val d = blockingQueue.poll()
        if (d != null) {
          println(d)
        }
      } catch (e: InterruptedException) {
        e.printStackTrace()
      }
    }
  }.start()

  repeat(5) {
    // 必须使用 delayQueue 的 offer() 方法。为什么有这么个规定?请看下文。
    delayQueue.offer("TEST-DATA$it", 5, TimeUnit.SECONDS)
    Thread.sleep(1000)
  }
  Thread.sleep(10000)
}

可以看到控制台在第五秒时,每隔一秒输出一行代码

以上实现核心有两个:poll()和offer()

先看offer()方法实现:

层层点进去后:

核心在最后一行这个lua命令,格式化后,再举例替换掉参数位:

lua 复制代码
-- 示例参数
local timeout = 1645833600 -- 2022年2月22日00:00:00(Unix 时间戳),到期时间戳
local random = 'abc123'
local encodedMessage = '{"id":1,"content":"Hello World"}' -- 假设这是编码后的消息内容

-- 构造消息值
local value = struct.pack('Bc0Lc0', string.len(random), random, string.len(encodedMessage), encodedMessage)

-- 添加到超时有序集合
redis.call('zadd', 'myTimeoutSetName', timeout, value)

-- 添加到普通队列
redis.call('rpush', 'myQueueName', value)

-- 发布通知
local v = redis.call('zrange', 'myTimeoutSetName', 0, 0)
if v[1] == value then
    redis.call('publish', 'myChannelName', timeout)
end

综合来看,这段代码实现了以下功能:

  • 创建一个包含到期时间戳、随机标识符和编码消息内容的复合二进制值。
  • 将该值同时存入一个有序集合(用于按到期时间排序和查询)和一个普通队列(用于常规消息处理)。
  • 当处理的消息恰好是有序集合中最接近到期的一个时,通过 Redis 的发布/订阅机制,在 myChannelName 频道发布一条消息,通知其他订阅者有消息即将到期。

pub/sub在这里是干什么的?看一幅图

上面箭头对应了代码创建的队列,pub/sub 机制是为了做队列数据转移工作的,即从前两个队列中移除过期元素到第三个队列中

kotlin 复制代码
@Autowired
private lateinit var redisson: RedissonClient

@Test
fun contextLoads() {
  val blockingQueue = redisson.getBlockingDeque<String>("TEST-QUEUE")
  val delayQueue = redisson.getDelayedQueue(blockingQueue)

  // part1
  Thread {
    while (true) {
      try {
        // 必须使用 blockingQueue 的 poll() 方法!
        val d = blockingQueue.poll()
        if (d != null) {
          println(d)
        }
      } catch (e: InterruptedException) {
        e.printStackTrace()
      }
    }
  }.start()

  // part2
  repeat(5) {
    // 必须使用 delayQueue 的 offer() 方法
    
    // 每五秒发送一个延时消息
    delayQueue.offer("TEST-DATA$it", 10, TimeUnit.SECONDS)
    Thread.sleep(5000)
  }


  //        blockingQueue.push("A")
  Thread.sleep(1000000)
}

为了方便你自己调试,我把代码贴在这,试试part1、2分别打开和关闭的组合多试几次,然后手动频繁刷新查看redis存储情况,就非常清楚了。

那转移工作怎么做的?

首先贴上关键代码位置

同样在这个类,其构造方法中,起了一个转移任务。

这样,便每次创建一个delay_queue,都会起一个转移任务,该任务负责

  • 异步将超时有序集合中的到期消息转移到普通队列,并从有序集合中移除。
  • 获取下一次任务的开始时间。
  • 返回一个 RTopic 实例,用于发布/订阅消息到期通知。

该任务怎么调度的?

源码位置在 queueTransferService.schedule(queueName, task)


相关推荐
qq_297574679 小时前
【实战教程】SpringBoot 实现多文件批量下载并打包为 ZIP 压缩包
java·spring boot·后端
计算机程序设计小李同学17 小时前
基于 Spring Boot + Vue 的龙虾专营店管理系统的设计与实现
java·spring boot·后端·spring·vue
Charlie_lll19 小时前
力扣解题-[3379]转换数组
数据结构·后端·算法·leetcode
VX:Fegn089519 小时前
计算机毕业设计|基于springboot + vue云租车平台系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
汤姆yu19 小时前
2026基于springboot的在线招聘系统
java·spring boot·后端
计算机学姐20 小时前
基于SpringBoot的校园社团管理系统
java·vue.js·spring boot·后端·spring·信息可视化·推荐算法
hssfscv20 小时前
Javaweb学习笔记——后端实战8 springboot原理
笔记·后端·学习
咚为20 小时前
Rust tokio:Task ≠ Thread:Tokio 调度模型中的“假并发”与真实代价
开发语言·后端·rust
Anastasiozzzz21 小时前
对抗大文件上传---分片加多重Hash判重
服务器·后端·算法·哈希算法
Vivienne_ChenW21 小时前
DDD领域模型在项目中的实战
java·开发语言·后端·设计模式