redssion 延迟队列源码分析

本文详细探讨了Redisson延迟队列的工作原理,通过一个简单的Java demo展示了其使用方式。Redisson延迟队列基于Redis实现,利用ZSet存储延迟触发时间,并结合Lua脚本确保操作原子性。在客户端,数据首先被添加到延迟队列,然后定时任务会检查并处理已到期的延迟任务,将它们推送到阻塞队列供消费者消费。整个流程高效且易于使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

         redssion 是个优秀的开源框架,里面实现了很多常用的redis操作。例 分布式锁及分布式延时队列,api使用操作简单,功能十分强大。

 如果想进一步了解可访问目录 · redisson/redisson Wiki · GitHub

        本次主要研究分析延迟队列的实现,废话不多说先上个简单的demo。

package com.xuyw.redisson;

import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RBlockingDeque;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

import java.util.concurrent.TimeUnit;

/**
 * Date: 2021-11-13 下午 9:44 <br>
 * Copyright (c) 2021 0ne.xu
 *
 * @author xuyw
 */
@Slf4j
public class DelayedQueueTest {
    public static void main(String[] args) throws InterruptedException {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        RedissonClient redissonClient = Redisson.create(config);
        RBlockingDeque<String> blockingDeque = redissonClient.getBlockingDeque("one.delayed.queue");
        RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
        //添加延时消息
        delayedQueue.offer("测试消息1", 10, TimeUnit.SECONDS);
        delayedQueue.offerAsync("测试消息offerAsync", 20, TimeUnit.SECONDS);
        //获取延时消息
        while (true) {
            String msg = blockingDeque.take();
            log.debug("获取延时队列消息:{}",msg);
        }
    }
}

上述代码已经是一个简单的api使用,短短十来行代码就实现了一个延迟队列的功能。阅读源码发现大概执行过程是这样的

  • 延时队列list:数据入队的队列
  • 目标队列list:过期数据所在的队列
  • timeoutSet过期时间zset:分数值为延时触发时间毫秒数

那么redssion 都帮我们干了些什么事呢?我们来调试分析。执行代码控制台输出日志,都是执行lua脚本。

3:36:37.535 [redisson-netty-1-2] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@1d0b9f08
23:36:37.569 [redisson-3-1] DEBUG org.redisson.command.CommandAsyncService - acquired connection for command (EVAL) and params [local expiredValues = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); if #exp..., 3, one.delayed.queue, redisson_delay_queue_timeout:{one.delayed.queue}, redisson_delay_queue:{one.delayed.queue}, 1636817797562, 100] from slot NodeSource [slot=null, addr=null, redisClient=null, redirect=null, entry=MasterSlaveEntry [masterEntry=[freeSubscribeConnectionsAmount=0, freeSubscribeConnectionsCounter=value:49:queue:0, freeConnectionsAmount=31, freeConnectionsCounter=value:63:queue:0, freezed=false, freezeReason=null, client=[addr=redis://127.0.0.1:6379], nodeType=MASTER, firstFail=0]]] using node 127.0.0.1/127.0.0.1:6379... RedisConnection@132632920 [redisClient=[addr=redis://127.0.0.1:6379], channel=[id: 0x7aa30e46, L:/127.0.0.1:57187 - R:127.0.0.1/127.0.0.1:6379], command=null]
23:36:37.577 [redisson-3-1] DEBUG org.redisson.command.CommandAsyncService - connection released for command (EVAL) and params [local expiredValues = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); if #exp..., 3, one.delayed.queue, redisson_delay_queue_timeout:{one.delayed.queue}, redisson_delay_queue:{one.delayed.queue}, 1636817797562, 100] from slot NodeSource [slot=null, addr=null, redisClient=null, redirect=null, entry=MasterSlaveEntry [masterEntry=[freeSubscribeConnectionsAmount=0, freeSubscribeConnectionsCounter=value:49:queue:0, freeConnectionsAmount=32, freeConnectionsCounter=value:64:queue:0, freezed=false, freezeReason=null, client=[addr=redis://127.0.0.1:6379], nodeType=MASTER, firstFail=0]]] using connection RedisConnection@132632920 [redisClient=[addr=redis://127.0.0.1:6379], channel=[id: 0x7aa30e46, L:/127.0.0.1:57187 - R:127.0.0.1/127.0.0.1:6379], command=null]
23:36:37.611 [main] DEBUG org.redisson.command.CommandAsyncService - acquired connection for command (EVAL) and params [local value = struct.pack('dLc0', tonumber(ARGV[2]), string.len(ARGV[3]), ARGV[3]);redis.call('zadd'..., 4, one.delayed.queue, redisson_delay_queue_timeout:{one.delayed.queue}, redisson_delay_queue:{one.delayed.queue}, redisson_delay_queue_channel:{one.delayed.queue}, 1636817807586, 3140263111307140513, PooledUnsafeDirectByteBuf(ridx: 0, widx: 15, cap: 256)] from slot NodeSource [slot=null, addr=null, redisClient=null, redirect=null, entry=MasterSlaveEntry [masterEntry=[freeSubscribeConnectionsAmount=0, freeSubscribeConnectionsCounter=value:49:queue:0, freeConnectionsAmount=31, freeConnectionsCounter=value:63:queue:0, freezed=false, freezeReason=null, client=[addr=redis://127.0.0.1:6379], nodeType=MASTER, firstFail=0]]] using node 127.0.0.1/127.0.0.1:6379... RedisConnection@1470699934 [redisClient=[addr=redis://127.0.0.1:6379], channel=[id: 0x9b0b1d00, L:/127.0.0.1:57189 - R:127.0.0.1/127.0.0.1:6379], command=null]
23:36:37.614 [redisson-netty-1-3] DEBUG org.redisson.command.CommandAsyncService - connection released for command (EVAL) and params [local value = struct.pack('dLc0', tonumber(ARGV[2]), string.len(ARGV[3]), ARGV[3]);redis.call('zadd'..., 4, one.delayed.queue, redisson_delay_queue_timeout:{one.delayed.queue}, redisson_delay_queue:{one.delayed.queue}, redisson_delay_queue_channel:{one.delayed.queue}, 1636817807586, 3140263111307140513, PooledUnsafeDirectByteBuf(ridx: 0, widx: 15, cap: 256)] from slot NodeSource [slot=null, addr=null, redisClient=null, redirect=null, entry=MasterSlaveEntry [masterEntry=[freeSubscribeConnectionsAmount=0, freeSubscribeConnectionsCounter=value:49:queue:0, freeConnectionsAmount=32, freeConnectionsCounter=value:64:queue:0, freezed=false, freezeReason=null, client=[addr=redis://127.0.0.1:6379], nodeType=MASTER, firstFail=0]]] using connection RedisConnection@1470699934 [redisClient=[addr=redis://127.0.0.1:6379], channel=[id: 0x9b0b1d00, L:/127.0.0.1:57189 - R:127.0.0.1/127.0.0.1:6379], command=CommandData [promise=RedissonPromise [promise=ImmediateEventExecutor$ImmediatePromise@39d57856(success)], command=(EVAL), params=[local value = struct.pack('dLc0', tonumber(ARGV[2]), string.len(ARGV[3]), ARGV[3]);redis.call('zadd'..., 4, one.delayed.queue, redisson_delay_queue_timeout:{one.delayed.queue}, redisson_delay_queue:{one.delayed.queue}, redisson_delay_queue_channel:{one.delayed.queue}, 1636817807586, 3140263111307140513, PooledUnsafeDirectByteBuf(ridx: 0, widx: 15, cap: 256)], codec=org.redisson.codec.FstCodec]]
23:36:37.615 [main] DEBUG org.redisson.command.CommandAsyncService - acquired connection for command (EVAL) and params [local value = struct.pack('dLc0', tonumber(ARGV[2]), string.len(ARGV[3]), ARGV[3]);redis.call('zadd'..., 4, one.delayed.queue, redisson_delay_queue_timeout:{one.delayed.queue}, redisson_delay_queue:{one.delayed.queue}, redisson_delay_queue_channel:{one.delayed.queue}, 1636817817615, 7572321326843145784, PooledUnsafeDirectByteBuf(ridx: 0, widx: 24, cap: 256)] from slot NodeSource [slot=null, addr=null, redisClient=null, redirect=null, entry=MasterSlaveEntry [masterEntry=[freeSubscribeConnectionsAmount=0, freeSubscribeConnectionsCounter=value:49:queue:0, freeConnectionsAmount=31, freeConnectionsCounter=value:63:queue:0, freezed=false, freezeReason=null, client=[addr=redis://127.0.0.1:6379], nodeType=MASTER, firstFail=0]]] using node 127.0.0.1/127.0.0.1:6379... RedisConnection@1265052639 [redisClient=[addr=redis://127.0.0.1:6379], channel=[id: 0x0945bf7b, L:/127.0.0.1:57190 - R:127.0.0.1/127.0.0.1:6379], command=null]
23:36:37.616 [main] DEBUG org.redisson.command.CommandAsyncService - acquired connection for command (BLPOP) and params [one.delayed.queue, 0] from slot NodeSource [slot=null, addr=null, redisClient=null, redirect=null, entry=MasterSlaveEntry [masterEntry=[freeSubscribeConnectionsAmount=0, freeSubscribeConnectionsCounter=value:49:queue:0, freeConnectionsAmount=30, freeConnectionsCounter=value:62:queue:0, freezed=false, freezeReason=null, client=[addr=redis://127.0.0.1:6379], nodeType=MASTER, firstFail=0]]] using node 127.0.0.1/127.0.0.1:6379... RedisConnection@1217958627 [redisClient=[addr=redis://127.0.0.1:6379], channel=[id: 0x167bf181, L:/127.0.0.1:57191 - R:127.0.0.1/127.0.0.1:6379], command=null]
23:36:37.623 [redisson-netty-1-6] DEBUG org.redisson.command.CommandAsyncService - connection released for command (EVAL) and params [local value = struct.pack('dLc0', tonumber(ARGV[2]), string.len(ARGV[3]), ARGV[3]);redis.call('zadd'..., 4, one.delayed.queue, redisson_delay_queue_timeout:{one.delayed.queue}, redisson_delay_queue:{one.delayed.queue}, redisson_delay_queue_channel:{one.delayed.queue}, 1636817817615, 7572321326843145784, PooledUnsafeDirectByteBuf(ridx: 0, widx: 24, cap: 256)] from slot NodeSource [slot=null, addr=null, redisClient=null, redirect=null, entry=MasterSlaveEntry [masterEntry=[freeSubscribeConnectionsAmount=0, freeSubscribeConnectionsCounter=value:49:queue:0, freeConnectionsAmount=31, freeConnectionsCounter=value:63:queue:0, freezed=false, freezeReason=null, client=[addr=redis://127.0.0.1:6379], nodeType=MASTER, firstFail=0]]] using connection RedisConnection@1265052639 [redisClient=[addr=redis://127.0.0.1:6379], channel=[id: 0x0945bf7b, L:/127.0.0.1:57190 - R:127.0.0.1/127.0.0.1:6379], command=CommandData [promise=RedissonPromise [promise=ImmediateEventExecutor$ImmediatePromise@302dbc5a(success)], command=(EVAL), params=[local value = struct.pack('dLc0', tonumber(ARGV[2]), string.len(ARGV[3]), ARGV[3]);redis.call('zadd'..., 4, one.delayed.queue, redisson_delay_queue_timeout:{one.delayed.queue}, redisson_delay_queue:{one.delayed.queue}, redisson_delay_queue_channel:{one.delayed.queue}, 1636817817615, 7572321326843145784, PooledUnsafeDirectByteBuf(ridx: 0, widx: 24, cap: 256)], codec=org.redisson.codec.FstCodec]]
23:36:47.611 [pool-1-thread-1] DEBUG org.redisson.command.CommandAsyncService - acquired connection for command (EVAL) and params [local expiredValues = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); if #exp..., 3, one.delayed.queue, redisson_delay_queue_timeout:{one.delayed.queue}, redisson_delay_queue:{one.delayed.queue}, 1636817807611, 100] from slot NodeSource [slot=null, addr=null, redisClient=null, redirect=null, entry=MasterSlaveEntry [masterEntry=[freeSubscribeConnectionsAmount=0, freeSubscribeConnectionsCounter=value:49:queue:0, freeConnectionsAmount=30, freeConnectionsCounter=value:62:queue:0, freezed=false, freezeReason=null, client=[addr=redis://127.0.0.1:6379], nodeType=MASTER, firstFail=0]]] using node 127.0.0.1/127.0.0.1:6379... RedisConnection@1012049217 [redisClient=[addr=redis://127.0.0.1:6379], channel=[id: 0x0f0824cf, L:/127.0.0.1:57188 - R:127.0.0.1/127.0.0.1:6379], command=null]
23:36:47.624 [redisson-netty-1-19] DEBUG org.redisson.command.CommandAsyncService - connection released for command (EVAL) and params [local expiredValues = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); if #exp..., 3, one.delayed.queue, redisson_delay_queue_timeout:{one.delayed.queue}, redisson_delay_queue:{one.delayed.queue}, 1636817807611, 100] from slot NodeSource [slot=null, addr=null, redisClient=null, redirect=null, entry=MasterSlaveEntry [masterEntry=[freeSubscribeConnectionsAmount=0, freeSubscribeConnectionsCounter=value:49:queue:0, freeConnectionsAmount=31, freeConnectionsCounter=value:63:queue:0, freezed=false, freezeReason=null, client=[addr=redis://127.0.0.1:6379], nodeType=MASTER, firstFail=0]]] using connection RedisConnection@1012049217 [redisClient=[addr=redis://127.0.0.1:6379], channel=[id: 0x0f0824cf, L:/127.0.0.1:57188 - R:127.0.0.1/127.0.0.1:6379], command=CommandData [promise=RedissonPromise [promise=ImmediateEventExecutor$ImmediatePromise@52344caf(success: 1636817817615)], command=(EVAL), params=[local expiredValues = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); if #exp..., 3, one.delayed.queue, redisson_delay_queue_timeout:{one.delayed.queue}, redisson_delay_queue:{one.delayed.queue}, 1636817807611, 100], codec=org.redisson.client.codec.LongCodec]]
23:36:47.636 [redisson-netty-1-26] DEBUG org.redisson.command.CommandAsyncService - connection released for command (BLPOP) and params [one.delayed.queue, 0] from slot NodeSource [slot=null, addr=null, redisClient=null, redirect=null, entry=MasterSlaveEntry [masterEntry=[freeSubscribeConnectionsAmount=0, freeSubscribeConnectionsCounter=value:49:queue:0, freeConnectionsAmount=32, freeConnectionsCounter=value:64:queue:0, freezed=false, freezeReason=null, client=[addr=redis://127.0.0.1:6379], nodeType=MASTER, firstFail=0]]] using connection RedisConnection@1217958627 [redisClient=[addr=redis://127.0.0.1:6379], channel=[id: 0x167bf181, L:/127.0.0.1:57191 - R:127.0.0.1/127.0.0.1:6379], command=CommandData [promise=RedissonPromise [promise=ImmediateEventExecutor$ImmediatePromise@d6ad958(success: 测试消息1)], command=(BLPOP), params=[one.delayed.queue, 0], codec=org.redisson.codec.FstCodec]]
23:36:47.637 [main] DEBUG com.example.spring_api.redisson.DelayedQueueTest - 获取延时队列消息:测试消息1
23:36:47.638 [main] DEBUG org.redisson.command.CommandAsyncService - acquired connection for command (BLPOP) and params [one.delayed.queue, 0] from slot NodeSource [slot=null, addr=null, redisClient=null, redirect=null, entry=MasterSlaveEntry [masterEntry=[freeSubscribeConnectionsAmount=0, freeSubscribeConnectionsCounter=value:49:queue:0, freeConnectionsAmount=31, freeConnectionsCounter=value:63:queue:0, freezed=false, freezeReason=null, client=[addr=redis://127.0.0.1:6379], nodeType=MASTER, firstFail=0]]] using node 127.0.0.1/127.0.0.1:6379... RedisConnection@1901855448 [redisClient=[addr=redis://127.0.0.1:6379], channel=[id: 0xd013455f, L:/127.0.0.1:57198 - R:127.0.0.1/127.0.0.1:6379], command=null]
23:36:57.711 [pool-1-thread-1] DEBUG org.redisson.command.CommandAsyncService - acquired connection for command (EVAL) and params [local expiredValues = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); if #exp..., 3, one.delayed.queue, redisson_delay_queue_timeout:{one.delayed.queue}, redisson_delay_queue:{one.delayed.queue}, 1636817817710, 100] from slot NodeSource [slot=null, addr=null, redisClient=null, redirect=null, entry=MasterSlaveEntry [masterEntry=[freeSubscribeConnectionsAmount=0, freeSubscribeConnectionsCounter=value:49:queue:0, freeConnectionsAmount=30, freeConnectionsCounter=value:62:queue:0, freezed=false, freezeReason=null, client=[addr=redis://127.0.0.1:6379], nodeType=MASTER, firstFail=0]]] using node 127.0.0.1/127.0.0.1:6379... RedisConnection@1633518428 [redisClient=[addr=redis://127.0.0.1:6379], channel=[id: 0x30e6c529, L:/127.0.0.1:57194 - R:127.0.0.1/127.0.0.1:6379], command=null]
23:36:57.722 [redisson-netty-1-10] DEBUG org.redisson.command.CommandAsyncService - connection released for command (EVAL) and params [local expiredValues = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); if #exp..., 3, one.delayed.queue, redisson_delay_queue_timeout:{one.delayed.queue}, redisson_delay_queue:{one.delayed.queue}, 1636817817710, 100] from slot NodeSource [slot=null, addr=null, redisClient=null, redirect=null, entry=MasterSlaveEntry [masterEntry=[freeSubscribeConnectionsAmount=0, freeSubscribeConnectionsCounter=value:49:queue:0, freeConnectionsAmount=31, freeConnectionsCounter=value:63:queue:0, freezed=false, freezeReason=null, client=[addr=redis://127.0.0.1:6379], nodeType=MASTER, firstFail=0]]] using connection RedisConnection@1633518428 [redisClient=[addr=redis://127.0.0.1:6379], channel=[id: 0x30e6c529, L:/127.0.0.1:57194 - R:127.0.0.1/127.0.0.1:6379], command=CommandData [promise=RedissonPromise [promise=ImmediateEventExecutor$ImmediatePromise@2fda92b1(success: 1636849100849)], command=(EVAL), params=[local expiredValues = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); if #exp..., 3, one.delayed.queue, redisson_delay_queue_timeout:{one.delayed.queue}, redisson_delay_queue:{one.delayed.queue}, 1636817817710, 100], codec=org.redisson.client.codec.LongCodec]]
23:36:57.731 [redisson-netty-1-5] DEBUG org.redisson.command.CommandAsyncService - connection released for command (BLPOP) and params [one.delayed.queue, 0] from slot NodeSource [slot=null, addr=null, redisClient=null, redirect=null, entry=MasterSlaveEntry [masterEntry=[freeSubscribeConnectionsAmount=0, freeSubscribeConnectionsCounter=value:49:queue:0, freeConnectionsAmount=32, freeConnectionsCounter=value:64:queue:0, freezed=false, freezeReason=null, client=[addr=redis://127.0.0.1:6379], nodeType=MASTER, firstFail=0]]] using connection RedisConnection@1901855448 [redisClient=[addr=redis://127.0.0.1:6379], channel=[id: 0xd013455f, L:/127.0.0.1:57198 - R:127.0.0.1/127.0.0.1:6379], command=CommandData [promise=RedissonPromise [promise=ImmediateEventExecutor$ImmediatePromise@5f0c0d5b(success: 测试消息offerAsync)], command=(BLPOP), params=[one.delayed.queue, 0], codec=org.redisson.codec.FstCodec]]
23:36:57.732 [main] DEBUG com.example.spring_api.redisson.DelayedQueueTest - 获取延时队列消息:测试消息offerAsync
23:36:57.732 [main] DEBUG org.redisson.command.CommandAsyncService - acquired connection for command (BLPOP) and params [one.delayed.queue, 0] from slot NodeSource [slot=null, addr=null, redisClient=null, redirect=null, entry=MasterSlaveEntry [masterEntry=[freeSubscribeConnectionsAmount=0, freeSubscribeConnectionsCounter=value:49:queue:0, freeConnectionsAmount=31, freeConnectionsCounter=value:63:queue:0, freezed=false, freezeReason=null, client=[addr=redis://127.0.0.1:6379], nodeType=MASTER, firstFail=0]]] using node 127.0.0.1/127.0.0.1:6379... RedisConnection@2032788060 [redisClient=[addr=redis://127.0.0.1:6379], channel=[id: 0x73f01205, L:/127.0.0.1:57196 - R:127.0.0.1/127.0.0.1:6379], command=null]

初始化RedissonClient 客户端之后,我们创建了一个队列名为one.delayed.queue的阻塞队列。且得到一个对应的延时队列。往延时队列添加了两条延时消息,接着就是从阻塞队列获取延迟消息。

打开redis客户端会发现Redisson会创建一个名字为redisson_delay_queue:{one.delayed.queue}的list结构的key

 还有一个名字为redisson_delay_queue_timeout:{one.delayed.queue}的zset数据结构的key

 还有一个我们申明的one.delayed.queue list数据结构

 

那么先看看RDelayedQueue

走读代码发现实现类是RedissonDelayedQueue

  protected RedissonDelayedQueue(QueueTransferService queueTransferService, Codec codec, final CommandAsyncExecutor commandExecutor, String name) {
        super(codec, commandExecutor, name);
        QueueTransferTask task = new QueueTransferTask(commandExecutor.getConnectionManager()) {
            protected RFuture<Long> pushTaskAsync() {
                return commandExecutor.evalWriteAsync(RedissonDelayedQueue.this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_LONG, "local expiredValues = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); if #expiredValues > 0 then for i, v in ipairs(expiredValues) do local randomId, value = struct.unpack('dLc0', v);redis.call('rpush', KEYS[1], value);redis.call('lrem', KEYS[3], 1, v);end; redis.call('zrem', KEYS[2], unpack(expiredValues));end; local v = redis.call('zrange', KEYS[2], 0, 0, 'WITHSCORES'); if v[1] ~= nil then return v[2]; end return nil;", Arrays.asList(RedissonDelayedQueue.this.getName(), RedissonDelayedQueue.this.timeoutSetName, RedissonDelayedQueue.this.queueName), new Object[]{System.currentTimeMillis(), Integer.valueOf(100)});
            }

            protected RTopic getTopic() {
                return new RedissonTopic(LongCodec.INSTANCE, commandExecutor, RedissonDelayedQueue.this.channelName);
            }
        };
        queueTransferService.schedule(this.queueName, task);
        this.queueTransferService = queueTransferService;
    }

实例化发现有个定时执行的lua脚本,这是核心我们聚焦一下这个脚本


--[[
 
struct.pack 不熟悉可以自行学习充电 https://www.cnblogs.com/aquester/p/9891466.html
我理解的大概意思就是保存多个字段到一个对象,有点像python的元组类型

Arrays.asList(RedissonDelayedQueue.this.getName(), RedissonDelayedQueue.this.timeoutSetName, RedissonDelayedQueue.this.queueName), new Object[]{System.currentTimeMillis(), Integer.valueOf(100)}
参数对应
ARGV[1]:当前系统时间毫秒数
ARGV[2]:批量获取size
key对应
KEYS[1]:申明的name
KEYS[2]:延时队列zset名,redisson_delay_queue_timeout:{申明的name}
KEYS[3]:list名,redisson_delay_queue:{申明的name}

首先获取redisson_delay_queue_timeout:{申明的name} 集合中失效时间在0-当前系统时间毫秒数的前100条记录
如果记录不为空则循环处理
  1 将redisson_delay_queue_timeout:{申明的name} 集合数据存入{申明的name}集合中
  2 移除redisson_delay_queue:{申明的name}集合中数据

lrem 语法 LREM key count VALUE

count > 0 : 从表头开始向表尾搜索,移除与 VALUE 相等的元素,数量为 COUNT 。
count < 0 : 从表尾开始向表头搜索,移除与 VALUE 相等的元素,数量为 COUNT 的绝对值。
count = 0 : 移除表中所有与 VALUE 相等的值。

最后移除本次从redisson_delay_queue_timeout:{申明的name} 集合获取的数据

重新获取redisson_delay_queue_timeout:{申明的name} 集合下一个延时触发时间

---]]

local expiredValues = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); 
if #expiredValues > 0
then 
 for i, v in ipairs(expiredValues) 
     do local randomId, value = struct.unpack('dLc0', v);
     redis.call('rpush', KEYS[1], value);
     redis.call('lrem', KEYS[3], 1, v);
  end; 
  redis.call('zrem', KEYS[2], unpack(expiredValues));
end; 
local v = redis.call('zrange', KEYS[2], 0, 0, 'WITHSCORES'); 
if v[1] ~= nil then 
return v[2]; 
end 
return nil;"

上面返回了一个最近的一个延时时间,redssion 会根据这个时间计算出下一次触发任务时间。利用

netty的HashedWheelTimer 自动触发
代码在 QueueTransferService
  private void scheduleTask(Long startTime) {
        QueueTransferTask.TimeoutTask oldTimeout = (QueueTransferTask.TimeoutTask)this.lastTimeout.get();
        if (startTime != null) {
            if (oldTimeout != null) {
                oldTimeout.getTask().cancel();
            }

            long delay = startTime.longValue() - System.currentTimeMillis();
            if (delay > 10L) {
                Timeout timeout = this.connectionManager.newTimeout(new TimerTask() {
                    public void run(Timeout timeout) throws Exception {
                        QueueTransferTask.this.pushTask();
                        QueueTransferTask.TimeoutTask currentTimeout = (QueueTransferTask.TimeoutTask)QueueTransferTask.this.lastTimeout.get();
                        if (currentTimeout.getTask() == timeout) {
                            QueueTransferTask.this.lastTimeout.compareAndSet(currentTimeout, (Object)null);
                        }

                    }
                }, delay, TimeUnit.MILLISECONDS);
                if (!this.lastTimeout.compareAndSet(oldTimeout, new QueueTransferTask.TimeoutTask(startTime.longValue(), timeout))) {
                    timeout.cancel();
                }
            } else {
                this.pushTask();
            }

        }
    }


offer方法都干了啥?

 RedissonDelayedQueue.java
public void offer(V e, long delay, TimeUnit timeUnit) {
        this.get(this.offerAsync(e, delay, timeUnit));
    }

    public RFuture<Void> offerAsync(V e, long delay, TimeUnit timeUnit) {
        long delayInMs = timeUnit.toMillis(delay);
        long timeout = System.currentTimeMillis() + delayInMs;
        long randomId = ThreadLocalRandom.current().nextLong();
        return this.commandExecutor.evalWriteAsync(this.getName(), this.codec, RedisCommands.EVAL_VOID, "local value = struct.pack('dLc0', tonumber(ARGV[2]), string.len(ARGV[3]), ARGV[3]);redis.call('zadd', KEYS[2], ARGV[1], value);redis.call('rpush', KEYS[3], value);local v = redis.call('zrange', KEYS[2], 0, 0); if v[1] == value then redis.call('publish', KEYS[4], ARGV[1]); end;", Arrays.asList(this.getName(), this.timeoutSetName, this.queueName, this.channelName), new Object[]{timeout, randomId, this.encode(e)});
    }

其实发现就是计算延时时间并且执行了一段lua脚本,使用lua脚本可以保证redis操作的原子性。

我们来看一下这脚本

--[[
 
struct.pack 不熟悉可以自行学习充电 https://www.cnblogs.com/aquester/p/9891466.html
我理解的大概意思就是保存多个字段到一个对象,有点像python的元组类型
Arrays.asList(this.getName(), this.timeoutSetName, this.queueName, this.channelName),
 new Object[]{timeout, randomId, this.encode(e)}
参数对应
ARGV[1]:timeout延时的时间
ARGV[2]:randomId随机id
ARGV[3]:内容
key对应
KEYS[1]:申明的name
KEYS[2]:延时队列zset名,redisson_delay_queue_timeout:{申明的name}
KEYS[3]:list名,redisson_delay_queue:{申明的name}
KEYS[4]:发布渠道名,redisson_delay_queue_channel:{申明的name}

解读代码:

申明一个对象value,第一个值是随机id,第二个值是内容长度,第三个值是具体内容
往延时队列redisson_delay_queue_timeout:{申明的name}添加value,且分值为超时时间
同时往redisson_delay_queue:{申明的name}list里添加了value
zrange redisson_delay_queue_timeout:{one.delayed.queue} 0 0
下标参数 start 和 stop 都以 0 为底,也就是说,以 0 表示有序集第一个成员
如果第一个元素等于value则
publish redisson_delay_queue_channel:{申明的name} 延时时间
---]]
local value = struct.pack('dLc0', tonumber(ARGV[2]), string.len(ARGV[3]), ARGV[3]);
redis.call('zadd', KEYS[2], ARGV[1], value);
redis.call('rpush', KEYS[3], value);
local v = redis.call('zrange', KEYS[2], 0, 0); 
if v[1] == value 
then 
   redis.call('publish', KEYS[4], ARGV[1]); 
end;
  • 首先对名为redisson_delay_queue_timeout:{申明的name}的zset做zadd操作,score为当前时间+延时时间的毫秒数。
  • 再对名为redisson_delay_queue:{申明的name}的list做rpush操作。
  • 之后判断redisson_delay_queue_timeout:{申明的name}第一个值是否是当前结构体,是的话发布延时消息。

总结一下

  • 客户端启动后在offer数据之前,会自动订阅一个名字为 redisson_delay_queue_channel:{你申明的name}的队列,同时 BLPOP key 0 无限监听一个阻塞队列
  • offer 数据不是直接投递用到了中转队列,取出最近一个延迟时间,然后发送消息通知
  • 客户端延时任务到期从redisson_delay_queue_timeout:{申明的name}获取数据,rpush到阻塞队列也就是定时执行的那段lua脚本逻辑
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值