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脚本逻辑