Redisson 延迟队列
分布式系统中的延迟队列解决方案
更新于 2026.03.27
本文目录 7 节

底层实现
先看整体结构——Redisson延迟队列底层由 Redis 中的两个数据结构配合实现。
底层其实就是 Redis 里的两个原生数据结构:一个 zset(有序集合)充当”等候室”,一个 list 充当真正的队列。
运行流程
第一步:offer() 投递消息
调用 delayedQueue.offer("START:123", 5000, MILLISECONDS) 时,Redisson 做的事情很简单:把消息写入 zset,score 设为 当前时间戳 + 5000ms,即”到期时间”。消息就静静躺在 zset 里,没有任何线程在等它。
第二步:内部定时任务轮询
Redisson 客户端启动时,会在后台开启一个定时任务(默认每 500ms 执行一次),执行一段 Lua 脚本,逻辑大致是:
-- 从 zset 中取出所有 score ≤ 当前时间戳的消息
local msgs = redis.call('zrangebyscore', zset_key, 0, now)
-- 把它们逐个 lpush 到 list 中
for _, msg in ipairs(msgs) do
redis.call('lpush', list_key, msg)
redis.call('zrem', zset_key, msg)
end
这一步是原子的(Lua 脚本在 Redis 里原子执行),所以不会出现”取出来但没放进去”的情况。
第三步:blockingQueue.take() 消费消息
消费者线程调用 blockingQueue.take(),底层是 Redis 的 BRPOP 命令——没消息时阻塞挂起,有消息时立刻返回。消费者拿到消息后,根据消息类型进行消费。
因此,在使用Redisson充当延迟队列时,常常需要两个Queue。分别是 RBlockingQueue 和 RDelayedQueue。
前者用来消费,后者用来投递,它们操作的是同一个 zset+list 组合。
生产与消费示例
// 1. 获取目标阻塞队列
RBlockingDeque<String> destinationQueue = redissonClient.getBlockingDeque("orderQueue");
// 2. 获取延迟队列(装饰目标队列)
RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(destinationQueue);
// 3. 生产者:发送延迟消息
// 10秒后消息会进入 orderQueue
delayedQueue.offer("order_12345", 10, TimeUnit.SECONDS);
// 4. 消费者:在另一个线程或服务中
new Thread(() -> {
while (true) {
try {
// 注意:消费时直接监听 destinationQueue
String orderId = destinationQueue.take();
System.out.println("收到过期订单: " + orderId);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
总结
Redisson 延迟队列的本质是用 Redis 原生数据结构模拟的定时投递机制,整个流程可以拆解为三个角色的协作:
RDelayedQueue(投递方):负责将消息写入 zset,score 为到期时间戳,消息在此躺着,什么也不干。
Redisson 内部定时任务(搬运工):每 500ms 扫描一次 zset,将到期消息原子地转移到 list,触发消费。
RBlockingQueue(消费方):底层通过 BRPOP 阻塞监听 list,消息一到立刻唤醒消费者线程处理。
相比 JDK 自带的 ScheduledExecutorService,这套机制最大的优势是调度状态外置于 Redis——消息不在进程内存中,服务重启后消息不丢失,多实例部署时也不会重复触发(配合分布式锁使用)。代价是引入了 500ms 左右的轮询延迟,以及对 Redis 可用性的依赖。
因此,它适合对触发精度要求在秒级、需要跨进程可靠调度的场景,
评论
无需登录,审核通过后公开。只有博主可以回复。
正在加载…
已公开的评论