MichelleChung 狮子领域 程序圈 2023-12-17 10:00 发表于辽宁
前言
在前几天查漏补缺的时候,看到了 Redisson 限流器 RateLimiter 的内容。Redisson 值得学习的底层原理其实挺多的,所以这次也花了一些时间去看源码和分析,这次关于 Lua 脚本的部分也进行了分析,重要的部分也画了流程图,希望可以帮助理解学习。
参考目录
https://github.com/oneone1995/blog/issues/13
不错的分析文章,但是是基于旧版本 Redisson,有些源码不太一样,本文是基于 Redisson 最新版 V3.17.2 来进行说明。
代码实现
首先需要说明一下,框架【RuoYi-Vue-Plus】中实现限流器也是基于 AOP 方式进行实现的。
1、自定义注解 @RateLimiter
com.ruoyi.common.annotation.RateLimiter
2、切面逻辑类:RateLimiterAspect
com.ruoyi.framework.aspectj.RateLimiterAspect
3、使用方式
使用方式很简单,也是在直接使用注解即可。也可以不设置参数,注解中都有设置默认值。com.ruoyi.demo.controller.RedisRateLimiterController#test
在 com.ruoyi.demo.controller.RedisRateLimiterController
中,有三种不同的限流方式示例,本文以第一种全局限流为例进行说明。
源码分析
切面逻辑方法 RateLimiterAspect#doBefore
比较简单,最重要的部分是获取令牌方法 RedisUtils#rateLimiter
,所以重点分析这个方法的逻辑。
RedisUtils#rateLimiter
方法步骤:
-
获取限流器;
-
根据自定义参数初始化限流器;
-
尝试从限流器中获取令牌,如果存在,返回可用令牌数,如果不存在,返回 -1。
源码分析1:初始化限流器
RedissonRateLimiter#trySetRate
Lua 脚本
redis.call('hsetnx', KEYS[1], 'rate', ARGV[1]);
redis.call('hsetnx', KEYS[1], 'interval', ARGV[2]);
return redis.call('hsetnx', KEYS[1], 'type', ARGV[3]);
脚本调用的方法参数对照表:
脚本参数名 | Java参数名 | 参数值 |
---|---|---|
KEYS[1] | getRawName() | 限流器 key |
ARGV[1] | rate | 限流器令牌数 |
ARGV[2] | unit.toMillis(rateInterval) | 时间间隔 |
ARGV[3] | type.ordinal() | 限流器类型(全局) |
Redis 实际执行结果
1654399761.149145 [5 lua] "hsetnx" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "rate" "2"
1654399761.149157 [5 lua] "hsetnx" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "interval" "100000"
1654399761.149168 [5 lua] "hsetnx" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "type" "0"
源码分析2:尝试获取令牌
RedissonRateLimiter#tryAcquire
RedissonRateLimiter#tryAcquireAsync
Lua 脚本
local rate = redis.call('hget', KEYS[1], 'rate');
local interval = redis.call('hget', KEYS[1], 'interval');
local type = redis.call('hget', KEYS[1], 'type');
assert(rate ~= false and interval ~= false and type ~= false, 'RateLimiter is not initialized')
local valueName = KEYS[2];
local permitsName = KEYS[4];
if type == '1' then
valueName = KEYS[3];
permitsName = KEYS[5];
end;
assert(tonumber(rate) >= tonumber(ARGV[1]), 'Requested permits amount could not exceed defined rate');
local currentValue = redis.call('get', valueName);
if currentValue ~= false then
local expiredValues = redis.call('zrangebyscore', permitsName, 0, tonumber(ARGV[2]) - interval);
local released = 0;
for i, v in ipairs(expiredValues) do
local random, permits = struct.unpack('Bc0I', v);
released = released + permits;
end;
if released > 0 then
redis.call('zremrangebyscore', permitsName, 0, tonumber(ARGV[2]) - interval);
if tonumber(currentValue) + released > tonumber(rate) then
currentValue = tonumber(rate) - redis.call('zcard', permitsName);
else
currentValue = tonumber(currentValue) + released;
end;
redis.call('set', valueName, currentValue);
end;
if tonumber(currentValue) < tonumber(ARGV[1]) then
local firstValue = redis.call('zrange', permitsName, 0, 0, 'withscores');
return 3 + interval - (tonumber(ARGV[2]) - tonumber(firstValue[2]));
else
redis.call('zadd', permitsName, ARGV[2], struct.pack('Bc0I', string.len(ARGV[3]), ARGV[3], ARGV[1]));
redis.call('decrby', valueName, ARGV[1]);
return nil;
end;
else
redis.call('set', valueName, rate);
redis.call('zadd', permitsName, ARGV[2], struct.pack('Bc0I', string.len(ARGV[3]), ARGV[3], ARGV[1]));
redis.call('decrby', valueName, ARGV[1]);
return nil;
end;
脚本调用的方法参数对照表:
脚本参数名 | Java参数名 | 参数值 |
---|---|---|
KEYS[1] | getRawName() | 限流器 key |
KEYS[2] | getValueName() | 限流器令牌数量 key |
KEYS[3] | getClientValueName() | 限流器客户端令牌数量 key |
KEYS[4] | getPermitsName() | 限流器令牌 key |
KEYS[5] | getClientPermitsName() | 限流器客户端令牌 key |
ARGV[1] | value | 请求限流器令牌数 |
ARGV[2] | System.currentTimeMillis() | 当前时间戳 |
ARGV[3] | random | 随机数 |
Lua 脚本逻辑流程简图(重要)
由于上面的脚本流程比较多,所以结合流程图来进行分析。
Lua 脚本代码分析
说明:
-
##
代表注释说明或者是请求。 -
##(数值)
代表第几次请求,后面紧跟着的是底层执行的命令。
### RateType:OVERALL
### rate:2
### rateInterval:100s
local rate = redis.call('hget', KEYS[1], 'rate');
## "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "rate"
local interval = redis.call('hget', KEYS[1], 'interval');
## "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "interval"
local type = redis.call('hget', KEYS[1], 'type');
## "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "type"
assert(rate ~= false and interval ~= false and type ~= false, 'RateLimiter is not initialized')
local valueName = KEYS[2];
local permitsName = KEYS[4];
## 单机模式
if type == '1' then
valueName = KEYS[3];
## "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value:89a74653-01ad-4f25-9698-0d891ef076f4"
permitsName = KEYS[5];
## "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits:89a74653-01ad-4f25-9698-0d891ef076f4"
end;
assert(tonumber(rate) >= tonumber(ARGV[1]), 'Requested permits amount could not exceed defined rate');
## 限流器令牌数 >= 默认令牌数
## tonumber(2) >= tonumber("1")
local currentValue = redis.call('get', valueName);
##1 "get" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" --- null
##2 "get" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" --- currentValue = 1
##3 "get" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" --- currentValue = 0
##4 "get" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" --- currentValue = 0
## 第一次请求 currentValue 为空,进入 else 分支
## 第二次请求 currentValue 不为空,进入 if 分支
if currentValue ~= false then
local expiredValues = redis.call('zrangebyscore', permitsName, 0, tonumber(ARGV[2]) - interval);
## 计算过期时间:系统当前时间 - 时间间隔
##2 "zrangebyscore" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" "0" "1654399668395"
##3 "zrangebyscore" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" "0" "1654399695739"
##4 "zrangebyscore" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" "0" "1654399900581"
## 遍历保存需要释放的令牌数量 released
local released = 0;
for i, v in ipairs(expiredValues) do
local random, permits = struct.unpack('Bc0I', v);
released = released + permits;
end;
## 有需要释放的令牌
if released > 0 then
redis.call('zremrangebyscore', permitsName, 0, tonumber(ARGV[2]) - interval);
## "zremrangebyscore" 移除有序集中,指定分数区间内的所有成员。(ZREMRANGEBYSCORE key min max)
##4 "zremrangebyscore" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" "0" "1654399900581"
if tonumber(currentValue) + released > tonumber(rate) then
## 当前令牌数 + 释放令牌数 > 指定令牌数
currentValue = tonumber(rate) - redis.call('zcard', permitsName);
## 当前令牌数 = 指定令牌数 - 指定key中元素数量
else
currentValue = tonumber(currentValue) + released;
## 当前令牌数 = 当前令牌数 + 释放令牌数
end;
redis.call('set', valueName, currentValue);
##4 "set" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" "2"
end;
## 当前令牌数小于所需令牌数
if tonumber(currentValue) < tonumber(ARGV[1]) then
local firstValue = redis.call('zrange', permitsName, 0, 0, 'withscores');
## 获取 key:"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" 第一个值
##3 "zrange" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" "0" "0" "withscores"
return 3 + interval - (tonumber(ARGV[2]) - tonumber(firstValue[2]));
## 返回剩余时间
##3 3 + 100 - (tonumber(1654399695739) - tonumber(1654399761148)) --- firstValue[2] = 1654399761148
##3 65,512
else
redis.call('zadd', permitsName, ARGV[2], struct.pack('Bc0I', string.len(ARGV[3]), ARGV[3], ARGV[1]));
## 存入新令牌数据
##2 "zadd" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" "1654399768395" "\bO^\xac\x8a\x84D\xe3\\\x01\x00\x00\x00"
##4 "zadd" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" "1654400000581" "\b\x8e\x12\\V\xce\x16h\xff\x01\x00\x00\x00"
redis.call('decrby', valueName, ARGV[1]);
## 将 key:"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" 存储的令牌数减 1
##2 "decrby" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" "1"
##4 "decrby" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" "1"
return nil;
end;
else
redis.call('set', valueName, rate);
## 在 key:"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" 存储限流器令牌数
##1 "set" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" "2"
redis.call('zadd', permitsName, ARGV[2], struct.pack('Bc0I', string.len(ARGV[3]), ARGV[3], ARGV[1]));
## struct.pack 数据打包 struct.pack(格式化字符串,需要打包的数据1,需要打包的数据2 …)
## 'Bc0I'
## "B" an unsigned char.
## "cn" a sequence of exactly n chars corresponding to a single Lua string
(if n <= 0 then
for packing - the string length is taken,
unpacking - the number value of the previous unpacked value which is not returned).
## "I" an unsigned int (4 bytes).
##1 "zadd" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" "1654399768395" "\bO^\xac\x8a\x84D\xe3\\\x01\x00\x00\x00"
redis.call('decrby', valueName, ARGV[1]);
## 将 key:"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" 存储的令牌数减 1
##1 "decrby" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" "1"
return nil;
end;
Redis 实际执行结果
在脚本中有很多判断的分支结构,为了进入不同的分支因此进行了四次请求测试:(限流器的参数:可用令牌数 2,时间间隔 100 秒)
-
第一次请求:发放令牌,正常访问;
-
第二次请求:发放令牌,正常访问;
-
第三次请求:限制访问;
-
前三次请求在时间间隔 100s 内完成;
-
第四次请求:间隔超过100s,发放令牌,正常访问;
### 第一次请求:
"5"
"rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" KEYS[1] getRawName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" KEYS[2] getValueName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value:89a74653-01ad-4f25-9698-0d891ef076f4" KEYS[3] getClientValueName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" KEYS[4] getPermitsName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits:89a74653-01ad-4f25-9698-0d891ef076f4" KEYS[5] getClientPermitsName()
"1" ARGV[1] value
"1654399761148" ARGV[2] System.currentTimeMillis()
"=y\xa3\"\x83\x00\xf1z" ARGV[3] random
### 执行结果:
1654399761.149821 [5 lua] "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "rate"
1654399761.149838 [5 lua] "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "interval"
1654399761.149850 [5 lua] "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "type"
1654399761.149864 [5 lua] "get" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value"
1654399761.149875 [5 lua] "set" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" "2"
1654399761.149893 [5 lua] "zadd" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" "1654399761148" "\b=y\xa3\"\x83\x00\xf1z\x01\x00\x00\x00"
1654399761.149914 [5 lua] "decrby" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" "1"
-------------------------------------
### 第二次请求:
"5"
"rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" KEYS[1] getRawName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" KEYS[2] getValueName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value:89a74653-01ad-4f25-9698-0d891ef076f4" KEYS[3] getClientValueName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" KEYS[4] getPermitsName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits:89a74653-01ad-4f25-9698-0d891ef076f4" KEYS[5] getClientPermitsName()
"1" ARGV[1]
"1654399768395" ARGV[2]
"O^\xac\x8a\x84D\xe3\\" ARGV[3] random
### 执行结果:
1654399768.397061 [5 lua] "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "rate"
1654399768.397078 [5 lua] "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "interval"
1654399768.397089 [5 lua] "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "type"
1654399768.397106 [5 lua] "get" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value"
1654399768.397125 [5 lua] "zrangebyscore" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" "0" "1654399668395"
1654399768.397150 [5 lua] "zadd" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" "1654399768395" "\bO^\xac\x8a\x84D\xe3\\\x01\x00\x00\x00"
1654399768.397174 [5 lua] "decrby" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" "1"
-------------------------------------
### 第三次请求:
"5"
"rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" KEYS[1] getRawName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" KEYS[2] getValueName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value:89a74653-01ad-4f25-9698-0d891ef076f4" KEYS[3] getClientValueName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" KEYS[4] getPermitsName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits:89a74653-01ad-4f25-9698-0d891ef076f4" KEYS[5] getClientPermitsName()
"1" ARGV[1]
"1654399795739" ARGV[2]
"yJ\xb1\xea\x99\x16\x9f\xcf" ARGV[3] random
### 执行结果:
1654399795.740534 [5 lua] "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "rate"
1654399795.740548 [5 lua] "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "interval"
1654399795.740558 [5 lua] "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "type"
1654399795.740571 [5 lua] "get" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value"
1654399795.740586 [5 lua] "zrangebyscore" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" "0" "1654399695739"
1654399795.740603 [5 lua] "zrange" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" "0" "0" "withscores"
-------------------------------------
### 第四次请求:
"5"
"rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" KEYS[1] getRawName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" KEYS[2] getValueName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value:89a74653-01ad-4f25-9698-0d891ef076f4" KEYS[3] getClientValueName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" KEYS[4] getPermitsName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits:89a74653-01ad-4f25-9698-0d891ef076f4" KEYS[5] getClientPermitsName()
"1" ARGV[1]
"1654400000581" ARGV[2]
"\x8e\x12\\V\xce\x16h\xff" ARGV[3] random
### 执行结果:
1654400000.583083 [5 lua] "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "rate"
1654400000.583098 [5 lua] "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "interval"
1654400000.583109 [5 lua] "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "type"
1654400000.583123 [5 lua] "get" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value"
1654400000.583140 [5 lua] "zrangebyscore" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" "0" "1654399900581"
1654400000.583167 [5 lua] "zremrangebyscore" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" "0" "1654399900581"
1654400000.583186 [5 lua] "set" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" "2"
1654400000.583202 [5 lua] "zadd" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" "1654400000581" "\b\x8e\x12\\V\xce\x16h\xff\x01\x00\x00\x00"
1654400000.583243 [5 lua] "decrby" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" "1"
源码分析3:返回可用令牌的数量
RedissonRateLimiter#availablePermits
Lua 脚本
local rate = redis.call('hget', KEYS[1], 'rate');
local interval = redis.call('hget', KEYS[1], 'interval');
local type = redis.call('hget', KEYS[1], 'type');
assert(rate ~= false and interval ~= false and type ~= false, 'RateLimiter is not initialized')
local valueName = KEYS[2];
local permitsName = KEYS[4];
if type == '1' then
valueName = KEYS[3];
permitsName = KEYS[5];
end;
local currentValue = redis.call('get', valueName);
if currentValue == false then
redis.call('set', valueName, rate);
return rate;
else
local expiredValues = redis.call('zrangebyscore', permitsName, 0, tonumber(ARGV[1]) - interval);
local released = 0;
for i, v in ipairs(expiredValues) do
local random, permits = struct.unpack('Bc0I', v);
released = released + permits;
end;
if released > 0 then
redis.call('zremrangebyscore', permitsName, 0, tonumber(ARGV[1]) - interval);
currentValue = tonumber(currentValue) + released;
redis.call('set', valueName, currentValue);
end;
return currentValue;
end;
脚本调用的方法参数对照表:
脚本参数名 | Java参数名 | 参数值 |
---|---|---|
KEYS[1] | getRawName() | 限流器 key |
KEYS[2] | getValueName() | 限流器令牌数量 key |
KEYS[3] | getClientValueName() | 限流器客户端令牌数量 key |
KEYS[4] | getPermitsName() | 限流器令牌 key |
KEYS[5] | getClientPermitsName() | 限流器客户端令牌 key |
ARGV[1] | System.currentTimeMillis() | 当前时间戳 |
Lua 脚本代码分析
该方法是在尝试获取令牌成功后执行的方法,源码中的逻辑实际上和尝试获取令牌中的逻辑是一样的,所以理解了上面的逻辑之后这里也就能够很容易梳理清楚。
说明:
-
##
代表注释说明或者是请求。 -
##(数值)
代表第几次请求,后面紧跟着的是底层执行的命令。
### RateType:OVERALL
### rate:2
### rateInterval:100s
local rate = redis.call('hget', KEYS[1], 'rate');
## "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "rate"
local interval = redis.call('hget', KEYS[1], 'interval');
## "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "interval"
local type = redis.call('hget', KEYS[1], 'type');
## "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "type"
assert(rate ~= false and interval ~= false and type ~= false, 'RateLimiter is not initialized')
local valueName = KEYS[2];
local permitsName = KEYS[4];
## 单机模式
if type == '1' then
valueName = KEYS[3];
## "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value:89a74653-01ad-4f25-9698-0d891ef076f4"
permitsName = KEYS[5];
## "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits:89a74653-01ad-4f25-9698-0d891ef076f4"
end;
local currentValue = redis.call('get', valueName);
##1 "get" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" --- currentValue = 1
##2 "get" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" --- currentValue = 0
##4 "get" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" --- currentValue = 1
## 第一次请求 currentValue 不为空,进入 else 分支
if currentValue == false then
redis.call('set', valueName, rate);
return rate;
else
local expiredValues = redis.call('zrangebyscore', permitsName, 0, tonumber(ARGV[1]) - interval);
## 计算过期时间:系统当前时间 - 时间间隔
##1 "zrangebyscore" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" "0" "1654399661149"
##2 "zrangebyscore" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" "0" "1654399668397"
##4 "zrangebyscore" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" "0" "1654399900582"
## 遍历保存需要释放的令牌数量 released
local released = 0;
for i, v in ipairs(expiredValues) do
local random, permits = struct.unpack('Bc0I', v);
released = released + permits;
end;
## 有需要释放的令牌
if released > 0 then
redis.call('zremrangebyscore', permitsName, 0, tonumber(ARGV[1]) - interval);
currentValue = tonumber(currentValue) + released;
redis.call('set', valueName, currentValue);
end;
return currentValue;
## 返回当前可用令牌数
end;
Redis 实际执行结果
四次请求测试:(限流器的参数:可用令牌数 2,时间间隔 100 秒)
-
第一次请求:发放令牌,正常访问;
-
第二次请求:发放令牌,正常访问;
-
第三次请求:没有进入该方法逻辑,限制访问;
-
前三次请求在时间间隔 100s 内完成;
-
第四次请求:间隔超过100s,发放令牌,正常访问;
### 第一次请求:
"5"
"rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" KEYS[1] getRawName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" KEYS[2] getValueName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value:89a74653-01ad-4f25-9698-0d891ef076f4" KEYS[3] getClientValueName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" KEYS[4] getPermitsName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits:89a74653-01ad-4f25-9698-0d891ef076f4" KEYS[5] getClientPermitsName()
"1654399761149" ARGV[1] System.currentTimeMillis()
### 执行结果:
1654399761.150591 [5 lua] "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "rate"
1654399761.150604 [5 lua] "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "interval"
1654399761.150615 [5 lua] "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "type"
1654399761.150638 [5 lua] "get" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value"
1654399761.150669 [5 lua] "zrangebyscore" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" "0" "1654399661149"
-------------------------------------
### 第二次请求:
"5"
"rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" KEYS[1] getRawName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" KEYS[2] getValueName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value:89a74653-01ad-4f25-9698-0d891ef076f4" KEYS[3] getClientValueName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" KEYS[4] getPermitsName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits:89a74653-01ad-4f25-9698-0d891ef076f4" KEYS[5] getClientPermitsName()
"1654399768397" ARGV[1] System.currentTimeMillis()
### 执行结果:
1654399768.398240 [5 lua] "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "rate"
1654399768.398256 [5 lua] "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "interval"
1654399768.398269 [5 lua] "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "type"
1654399768.398281 [5 lua] "get" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value"
1654399768.398304 [5 lua] "zrangebyscore" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" "0" "1654399668397"
-------------------------------------
### 第四次请求:
"5"
"rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" KEYS[1] getRawName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value" KEYS[2] getValueName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value:89a74653-01ad-4f25-9698-0d891ef076f4" KEYS[3] getClientValueName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" KEYS[4] getPermitsName()
"{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits:89a74653-01ad-4f25-9698-0d891ef076f4" KEYS[5] getClientPermitsName()
"1654400000582" ARGV[1] System.currentTimeMillis()
### 执行结果:
1654400000.584141 [5 lua] "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "rate"
1654400000.584168 [5 lua] "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "interval"
1654400000.584199 [5 lua] "hget" "rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test" "type"
1654400000.584210 [5 lua] "get" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:value"
1654400000.584239 [5 lua] "zrangebyscore" "{rate_limit:com.ruoyi.demo.controller.RedisRateLimiterController-test}:permits" "0" "1654399900582"