【RuoYi-Vue-Plus】学习笔记 26 - Redisson(四) 限流器 RateLimiter
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' thenvalueName = 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 thenlocal expiredValues = redis.call('zrangebyscore', permitsName, 0, tonumber(ARGV[2]) - interval);local released = 0;for i, v in ipairs(expiredValues) dolocal random, permits = struct.unpack('Bc0I', v);released = released + permits;end;if released > 0 thenredis.call('zremrangebyscore', permitsName, 0, tonumber(ARGV[2]) - interval);if tonumber(currentValue) + released > tonumber(rate) thencurrentValue = tonumber(rate) - redis.call('zcard', permitsName);elsecurrentValue = tonumber(currentValue) + released;end;redis.call('set', valueName, currentValue);end;if tonumber(currentValue) < tonumber(ARGV[1]) thenlocal firstValue = redis.call('zrange', permitsName, 0, 0, 'withscores');return 3 + interval - (tonumber(ARGV[2]) - tonumber(firstValue[2]));elseredis.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;elseredis.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:100slocal 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' thenvalueName = 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 thenlocal 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"## 遍历保存需要释放的令牌数量 releasedlocal released = 0;for i, v in ipairs(expiredValues) dolocal random, permits = struct.unpack('Bc0I', v);released = released + permits;end;## 有需要释放的令牌if released > 0 thenredis.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中元素数量elsecurrentValue = 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]) thenlocal 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,512elseredis.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;elseredis.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 thenfor 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' thenvalueName = KEYS[3];permitsName = KEYS[5];end;local currentValue = redis.call('get', valueName);if currentValue == false thenredis.call('set', valueName, rate);return rate;elselocal expiredValues = redis.call('zrangebyscore', permitsName, 0, tonumber(ARGV[1]) - interval);local released = 0;for i, v in ipairs(expiredValues) dolocal random, permits = struct.unpack('Bc0I', v);released = released + permits;end;if released > 0 thenredis.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:100slocal 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' thenvalueName = 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 thenredis.call('set', valueName, rate);return rate;elselocal 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"## 遍历保存需要释放的令牌数量 releasedlocal released = 0;for i, v in ipairs(expiredValues) dolocal random, permits = struct.unpack('Bc0I', v);released = released + permits;end;## 有需要释放的令牌if released > 0 thenredis.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"
更多推荐



所有评论(0)