redis+lua实现限流

Redis + Lua 实现限流的核心优势在于 Lua 脚本的执行是原子性的

常见的限流算法,这里讲三种,固定窗限流,滑动窗限流和令牌桶限流

使用的是redis的eval命令

EVAL script numkeys [key [key ...]] [arg [arg ...]]

script ---脚本

numkeys --指定键的数量,前几个是键

key \[key ...\]\] 这一截就是键 \[arg \[arg ...\]\] 这一截就是参数 ### 一、固定窗限流 #### 1.思路 先来考虑一下思路,采用redis的eval命令执行lua做:实现一个1秒只能通过5个请求; 需要一个key存储一个int值,用存的值和5做比较,小于5能通过,值加1,否则不能通过,因为每1秒5个请求,所以key的有效时长设置成1秒,这样下一秒的请求过来,前面的key实失效了,这样又能继续通过 这里每次加1使用redis的自增命令即可 > 每次请求过来key的有效时间刷不刷新呢? > > 不能,假设当0.8秒来了一个请求,正好是第5个请求,那么现在满了,第6个请求是1.2秒过来的,现在已经是下一秒了,按理说应该通过, 但是由于前一秒的key还存在,且值已经等于5了,所以通过不了,因此每秒第一次设置有效时长就可以了 #### 2.编写lua脚本 ```lua local key = KEYS[1] --这个是redis键 local time = ARGV[1] --时间 local limit = ARGV[2] --允许通过的数量 local value = redis.call("incr",key) --执行incr自增命令,返回key对应的value if(value == 1) --值为1,说明第一次设置一下有效时长 then redis.call("expire",key,time) end return value >= limit and 1 or 0 --判断1就是能通过,0就是不能通过 ``` 执行EVAL script 1 key123 1 5 ### 二、滑动窗限流 所谓滑动窗采用的是redis的有序列表 使用到的命令: ZREMRANGEBYRANK key start stop 删除有序列表索引从start 到stop (包含start,stop) 的值 添加命令:ZADD key score member 该命令会使用score值来排序 #### 1.思路 还是假设:每秒只能通过5个请求,我们加入列表的命令的score可以使用时间戳,这样我们每次请求过来就干掉小于当前时间戳的元素,用剩下的元素个数和5做比较即可, #### 2.编写lua脚本 ```lua local key = KEYS[1] -- redis的键,存列表的 local capacity = ARGV[1] -- 通过的数量 local unit_time = ARGV[2] -- 单位时间 local uuid = ARGV[3] -- uuid local current_temp = redis.call("TIME")[1] -- 当前时间戳 -- 删除单位时间之前的列表成员 redis.call("ZREMRANGEBYRANK",key,0,current_temp - unit_time * 1000) -- 获取列表成员数量 local num = redis.call("ZCARD",key) -- 比较容量和列表成员大小 local differ = capacity > tonumber(num) -- 容量大于列表成员数量,则添加成员,并设置过期时间 if(differ) then redis.call("ZADD", key,current_temp,uuid) redis.call("expire", key,tonumber(unit_time)); end return differ ``` 执行EVAL script 1 key123 5 1 uuid 这样就达到了效果 ### 三、令牌桶算法 #### 1.思路 令牌桶稍微复杂点,我们通过令牌是否还有剩余来判断的 假设:每2秒8个请求 (1).首先需要一个key来存令牌数量,嗯就是桶,判断桶的数量是否大于1,大于有令牌,可以拿到令牌(请求通过),没有那就不行。这个key的有效时间为单位时间,每次执行都刷新有效时长,假设0.5秒来了三个请求那么桶还有5个令牌,1秒又来了3个请求,桶里还有2个令牌,2秒又来了2个请求,ok现在桶里没有令牌了,但是由于时间每次都会刷新现在key还是存在的,那么2.5秒又来一个请求,这个请求应该能通过,因为令牌的生产速度是每秒4个令牌 (2)还需要一个key存上次的刷新时间,来计算桶里的生产速度(当前时间戳-上次刷新时间戳)\* 生产速度 #### 2.编写lua脚本 ```lua local tokens_key = KEYS[1] -- 存桶数量的key local timestamp_key = KEYS[2] -- 存上次刷新时间的key local capacity = tonumber(ARGV[1]) -- 桶容量 local time = tonumber(ARGV[2]) -- key的有效时间 local now = redis.call("TIME")[1] -- 当前时间戳 local speed = capacity/time -- 将桶装满的时间(容量/有效时间) -- 桶剩余令牌数,不存在就设置为capacity local remain_capacity = tonumber(redis.call('get',tokens_key)) or capacity -- 上次刷新时间,不存在就设置为 0 local last_refresh_time = tonumber(redis.call('get',timestamp_key)) or 0 --计算两次时间差 local delta = math.max(0, now - last_refresh_time) --计算当前令牌数量,不能大于容量 local num = min(capacity,remain_capacity + delta * speed ) --判断如果大于1,则将cur_num 设置成 当前数量-1,否则不变 local cur_num = num if(num >= 1) then cur_num = num - 1 end --设置tokens_key的值为 cur_num,有效时间为time --设置timestamp_key的值为now,有效时间为time redis.call("setex", tokens_key, time, cur_num) redis.call("setex", timestamp_key, time, now) return num >= 1 and 1 or 0 ``` 执行EVAL script 1 key123 time123 8 2 > 如果将令牌桶算法的有效时长每次请求不刷新,那么就不需要第二个key,会发现和固定窗算法很像,为什么会出现两种实现方式,在系统中应用时,作用区别在哪里?

相关推荐
怡人蝶梦43 分钟前
Java后端技术栈问题排查实战:Spring Boot启动慢、Redis缓存击穿与Kafka消费堆积
java·jvm·redis·kafka·springboot·prometheus
瓯雅爱分享1 小时前
MES管理系统:Java+Vue,含源码与文档,实现生产过程实时监控、调度与优化,提升制造企业效能
java·mysql·vue·软件工程·源代码管理
鬼多不菜2 小时前
一篇学习CSS的笔记
java·前端·css
深色風信子2 小时前
Eclipse 插件开发 5.3 编辑器 监听输入
java·eclipse·编辑器·编辑器 监听输入·插件 监听输入
Blossom.1182 小时前
人工智能在智能健康监测中的创新应用与未来趋势
java·人工智能·深度学习·机器学习·语音识别
shangjg32 小时前
Kafka 如何保证不重复消费
java·分布式·后端·kafka
无处不在的海贼2 小时前
小明的Java面试奇遇之互联网保险系统架构与性能优化
java·面试·架构
Layux2 小时前
flowable候选人及候选人组(Candidate Users 、Candidate Groups)的应用包含拾取、归还、交接
java·数据库
Mylvzi2 小时前
Spring Boot 中 @RequestParam 和 @RequestPart 的区别详解(含实际项目案例)
java·spring boot·后端