【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. 获取限流器;

  2. 根据自定义参数初始化限流器;

  3. 尝试从限流器中获取令牌,如果存在,返回可用令牌数,如果不存在,返回 -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 秒)

  1. 第一次请求:发放令牌,正常访问;

  2. 第二次请求:发放令牌,正常访问;

  3. 第三次请求:限制访问;

  4. 前三次请求在时间间隔 100s 内完成;

  5. 第四次请求:间隔超过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 秒)

  1. 第一次请求:发放令牌,正常访问;

  2. 第二次请求:发放令牌,正常访问;

  3. 第三次请求:没有进入该方法逻辑,限制访问;

  4. 前三次请求在时间间隔 100s 内完成;

  5. 第四次请求:间隔超过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"

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
概要介绍: 本课程主要是介绍并实战一款java中间件~redisson,介绍redisson相关的核心技术栈及其典型的应用场景,其中的应用场景就包括布隆过滤限流、短信发送、实时/定时邮件发送、数据字典、分布式服务调度等等,在业界号称是在java项目里正确使用redis的姿势。本课程的目标就在于带领各位小伙伴一起学习、攻克redisson,更好地巩固自己的核心竞争力,而至于跳槽涨薪,自然不在话下!  课程内容: 说起redisson,可能大伙儿不是很熟悉,但如果说起redis,想必肯定很多人都晓得。没错,这家伙字如其名,它就是架设在redis基础上的一款综合性的、新型的中间件,号称是java企业级应用开发中正确使用redis的姿势/客户端实例。 它是架设在redis基础之上,但拥有的功能却远远多于原生Redis 所提供的,比如分布式对象、分布式集合体系、分布式锁以及分布式服务调度等一系列具有分布式特性的对象实例… 而这些东西debug将在本门课程进行淋漓尽致的介绍并实战,除此之外,我们将基于spring boot2.0搭建的多模块项目实战典型的应用场景:对象存储、数据字典、短信发送、实时/定时邮件发送、布隆过滤限流组件、分布式服务调度....课程大纲如下所示: 下面罗列一下比较典型的核心技术栈及其实际业务场景的实战,如下图所示为redisson基于订阅-发布模式的核心技术~主题Topic的实际业务场景,即实时发送邮件: 而下图则是基于“多值映射MultiMap”数据结构实战实现的关于“数据字典”的缓存管理: 除此之外,我们还讲解了可以与分布式服务调度中间件dubbo相媲美的功能:分布式远程服务调度,在课程中我们动手搭建了两个项目,用于分别充当“生产者”与“消费者”角色,最终通过redisson的“服务调度组件”实现服务与服务之间、接口与接口之间的调用!  课程收益: (1)认识并掌握redisson为何物、常见的几种典型数据结构-分布式对象、集合、服务的应用及其典型应用场景的实战; (2)掌握如何基于spring boot2.0整合redisson搭建企业级多模块项目,并以此为奠基,实战企业级应用系统中常见的业务场景,巩固相应的技术栈! (3)站在项目管理与技术精进的角度,掌握对于给定的功能模块进行业务流程图的绘制、分析、模块划分、代码实战与性能测试和改进,提高编码能力与其他软实力; (4)对于Java微服务、分布式、springboot精进者而言,学完本课程,不仅可以巩固提高中间件的实战能力,其典型的应用场景更有助于面试、助力相关知识点的扫盲! 如下图所示: 关键字:Spring Boot,Redis,缓存穿透,缓存击穿,缓存雪崩,红包系统,Mybatis,高并发,多线程并发编程,发送邮件,列表List,集合Set,排行榜,有序集合SortedSet,哈希Hash ,进阶实战,面试,微服务、分布式 适用人群:redisson学习者,分布式中间件实战者,微服务学习者,java学习者,spring boot进阶实战者,redis进阶实战者

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值