【RuoYi-Vue-Plus】学习笔记 15 - Redisson(一) 延迟队列 Delayed Queue 添加队列数据(Redisson 源码 + Lua 脚本)

前言

这段时间在补 Spring Cloud 的视频,本来是打算为了学习【RuoYi-Cloud-Plus】做准备的。然后今天在群里被狮子大佬催更了(捂脸),大佬说可以分析一下【RuoYi-Vue-Plus】4.0版本新增功能,Redisson 分布式队列

其实之前还没正式发布的时候,我在 dev 分支就看了一下这块内容,但是打开源码全是 Lua 脚本,我就默默关掉了……

不过前段时间补了一下 Redis 6 的一些相关知识,里面有讲过关于 Lua 脚本的内容,虽然讲得比较简单,不过还算是能听懂一些,趁着有些时间,又去找了一些资料,发现慢慢看还是可以看懂大概,所以就赶紧写一下这篇博客,当然只是从比较简单的延迟队列开始,后续有时间会继续分析其他队列。

参考目录

代码实现

官方文档的demo(比较简单):
在这里插入图片描述

框架实现:

  • 添加队列数据

DelayedQueueController#add
在这里插入图片描述
QueueUtils#addDelayedQueueObject
在这里插入图片描述

功能调用

流程:
1、登录前端页面,获取 token,设置接口文档全局参数。
在这里插入图片描述
2、接口文档中调用【延迟队列 - 订阅队列】接口
在这里插入图片描述
3、接口文档中调用【延迟队列 - 添加队列数据】接口
在这里插入图片描述
4、查看控制台参数打印
在这里插入图片描述

源码分析

1、新建延迟队列

参考 QueueUtils#addDelayedQueueObject 方法可知,在添加数据前,先调用了 getDelayedQueue 方法获取一个延迟队列。
在这里插入图片描述
RedissonDelayedQueue#RedissonDelayedQueue
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、添加数据

在这里插入图片描述

3、Lua 脚本分析

这两个方法主要的操作步骤都是由 Lua 脚本来完成的,一开始就看脚本会容易懵逼,要看懂的话可以借助反向推导的方式,直接先看 Redis 做了什么操作。

操作方式:
1、redis-cli 打开 Redis 控制台。
如果是和我一样是 windows 本地的,可以直接打开 redis-cli.exe

2、输入命令 minitor 监控 Redis 操作。

3、直接调用添加队列数据接口,可以看到以下输出:
在这里插入图片描述
这里图不太直观,所以我在前面单独整理了一份,可能某些时间参数不一样,但是总体的过程是一样的。

总体执行分成两部分:第一部分是添加数据,第二部分是获取数据。

第一部分

输出结果:

1645518012.326477 [0 127.0.0.1:50852] "EVAL" "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;" "4" "zlyx" "redisson_delay_queue_timeout:{zlyx}" "redisson_delay_queue:{zlyx}" "redisson_delay_queue_channel:{zlyx}" "1645518017326" "2476180431764309577" "\"0001\""

1645518012.326784 [0 lua] "zadd" "redisson_delay_queue_timeout:{zlyx}" "1645518017326" "\xe3\x8a\xdd}\x94.\xc1C\x06\x00\x00\x00\"0001\""

1645518012.326868 [0 lua] "rpush" "redisson_delay_queue:{zlyx}" "\xe3\x8a\xdd}\x94.\xc1C\x06\x00\x00\x00\"0001\""

1645518012.326925 [0 lua] "zrange" "redisson_delay_queue_timeout:{zlyx}" "0" "0"

1645518012.326988 [0 lua] "publish" "redisson_delay_queue_channel:{zlyx}" "1645518017326"

参数分析:
在这里插入图片描述
脚本调用的方法参数对照表:

脚本参数名Java参数名参数值
KEYS[1]getRawName()“zlyx”
KEYS[2]timeoutSetName“redisson_delay_queue_timeout:{zlyx}”
KEYS[3]queueName“redisson_delay_queue:{zlyx}”
KEYS[4]channelName“redisson_delay_queue_channel:{zlyx}”
ARGV[1]timeout“1645518017326”
ARGV[2]randomId“2476180431764309577”
ARGV[3]encode(e)“\xe3\x8a\xdd}\x94.\xc1C\x06\x00\x00\x00"0001"”

脚本分析:

# struct.pack 用于数据打包, struct.pack(格式化字符串,需要打包的数据1,需要打包的数据2…), 此处打包队列内容信息为 value
local value = struct.pack('dLc0', tonumber(ARGV[2]), string.len(ARGV[3]), ARGV[3]); 

# 将元素 value 加入到 Sorted Set, KEY 为 "timeoutSetName" 的延时队列, "timeout" 为延时时间
redis.call('zadd', KEYS[2], ARGV[1], value);

## 转换为简易命令: "zadd" "timeoutSetName" "timeout" value
## 实际输出: "zadd" "redisson_delay_queue_timeout:{zlyx}" "1645518017326" "\xe3\x8a\xdd}\x94.\xc1C\x06\x00\x00\x00\"0001\""

# 将元素 value 加入到 List, KEY 为 queueName, 从右侧加入, 这是个普通 List
redis.call('rpush', KEYS[3], value);

## 转换为简易命令: "rpush" queueName value
## 实际输出: "rpush" "redisson_delay_queue:{zlyx}" "\xe3\x8a\xdd}\x94.\xc1C\x06\x00\x00\x00\"0001\""

# 延时队列 Sorted Set (KEY 为 "timeoutSetName") 从小到大排序, 并从中取出第一个元素
local v = redis.call('zrange', KEYS[2], 0, 0); 

## 转换为简易命令: "zrange" "timeoutSetName" "0" "0"
## 实际输出: "zrange" "redisson_delay_queue_timeout:{zlyx}" "0" "0"

if v[1] == value 
    then 
    # 如果第一个元素为当前新增的元素, 发布到 channel 中 "channelName"
    redis.call('publish', KEYS[4], ARGV[1]); 
end;

## 转换为简易命令: "publish" "channelName" "timeout"
## 实际输出: "publish" "redisson_delay_queue_channel:{zlyx}" "1645518017326"
第二部分

输出结果:

1645518017.426758 [0 127.0.0.1:50849] "EVAL" "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;" "3" "zlyx" "redisson_delay_queue_timeout:{zlyx}" "redisson_delay_queue:{zlyx}" "1645518017426" "100"

1645518017.426955 [0 lua] "zrangebyscore" "redisson_delay_queue_timeout:{zlyx}" "0" "1645518017426" "limit" "0" "100"

1645518017.426997 [0 lua] "rpush" "zlyx" "\"0001\""

1645518017.427013 [0 lua] "lrem" "redisson_delay_queue:{zlyx}" "1" "\xe3\x8a\xdd}\x94.\xc1C\x06\x00\x00\x00\"0001\""

1645518017.427033 [0 lua] "zrem" "redisson_delay_queue_timeout:{zlyx}" "\xe3\x8a\xdd}\x94.\xc1C\x06\x00\x00\x00\"0001\""

1645518017.427054 [0 lua] "zrange" "redisson_delay_queue_timeout:{zlyx}" "0" "0" "WITHSCORES"

1645518017.427970 [0 127.0.0.1:50853] "BLPOP" "zlyx" "0"

参数分析:
在这里插入图片描述
脚本调用的方法参数对照表:

脚本参数名Java参数名参数值
KEYS[1]getRawName()“zlyx”
KEYS[2]timeoutSetName“redisson_delay_queue_timeout:{zlyx}”
KEYS[3]queueName“redisson_delay_queue:{zlyx}”
ARGV[1]System.currentTimeMillis()“1645518017326”
ARGV[2]100100

脚本分析:

## 从 Sorted Set 按 score 从小到大排序,拿出小于当前时间的 100 条数据(已过期的数据)
local expiredValues = redis.call('zrangebyscore', KEYS[2], 0, ARGV[1], 'limit', 0, ARGV[2]); 

## 转换为简易命令: "zrangebyscore" "timeoutSetName" "0" "timeout" "limit" "0" "100"
## 实际输出: "zrangebyscore" "redisson_delay_queue_timeout:{zlyx}" "0" "1645518017426" "limit" "0" "100"

if #expiredValues > 0 
    then 
    ## 循环遍历
    for i, v in ipairs(expiredValues)
    ## struct.unpack 用于数据解包, struct.unpack(格式化字符串,需要解包的字节数组,开始解包的位置), 得到数据 v
    do local randomId, value = struct.unpack('dLc0', v);
	
        ## 加入到阻塞队列 queueName
        redis.call('rpush', KEYS[1], value);
		
## 转换为简易命令: "rpush" getRawName() value
## 实际输出: "rpush" "zlyx" "\"0001\""
		
        ## 删除 queueName 队列 (List) 该元素 v
        redis.call('lrem', KEYS[3], 1, v);
		
## 转换为简易命令: "lrem" queueName "1" v
## 实际输出: "lrem" "redisson_delay_queue:{zlyx}" "1" "\xe3\x8a\xdd}\x94.\xc1C\x06\x00\x00\x00\"0001\""
		
    end; 
	
    ## 删除 Sorted Set 中的该元素
    redis.call('zrem', KEYS[2], unpack(expiredValues));    
	
## 转换为简易命令: "zrem" timeoutSetName unpack(expiredValues)
## 实际输出: "zrem" "redisson_delay_queue_timeout:{zlyx}" "\xe3\x8a\xdd}\x94.\xc1C\x06\x00\x00\x00\"0001\""
	
end; 

## 取出 Sorted Set 中 score 最小的元素返回
local v = redis.call('zrange', KEYS[2], 0, 0, 'WITHSCORES'); 

## 转换为简易命令: "zrange" timeoutSetName "0" "0" "WITHSCORES"
## 实际输出: "zrange" "redisson_delay_queue_timeout:{zlyx}" "0" "0" "WITHSCORES"

if v[1] ~= nil 
    then return v[2];     
end 
return nil;
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MichelleChung

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值