Redis 限流
Redis 限流
·
Redis 限流
一:基于Redis的setnx、zset
1、setnx
比如需要在10秒内限定20个请求,那么我们在setnx的时候可以设置过期时间10,当请求的setnx数量达到20时候即达到了限流效果。
缺点:
- Redis中需要保持N个key
- 固定窗口统计
2、zset
类似滑动窗口,可以将请求组成一个zset数组,当每一次请求进来的时候,value保持唯一,可以用UUID生成,而score可以用当前时间戳表示,因为score我们可以用来计算当前时间戳之内有多少的请求数量。而zset数据结构也提供了range方法让我们可以很轻易的获取到2个时间戳内有多少请求,
缺点:
- zset的数据结构会越来越大
二: Redis + Lua实现限流
Lua脚本的优点:
- 减少网络开销:使用Lua脚本,无需向Redis 发送多次请求,执行一次即可,减少网络传输
- 原子操作:Redis 将整个Lua脚本作为一个命令执行,原子,无需担心并发
- 复用:Lua脚本一旦执行,会永久保存 Redis 中,,其他客户端可复用
Lua脚本大致逻辑如下:
-- 获取调用脚本时传入的第一个key值(用作限流的 key)
local key = KEYS[1]
-- 获取调用脚本时传入的第一个参数值(限流大小)
local limit = tonumber(ARGV[1])
-- 获取当前流量大小
local curentLimit = tonumber(redis.call('get', key) or "0")
-- 是否超出限流
if curentLimit + 1 > limit then
-- 返回(拒绝)
return 0
else
-- 没有超出 value + 1
redis.call("INCRBY", key, 1)
-- 设置过期时间
redis.call("EXPIRE", key, 2)
-- 返回(放行)
return 1
end
- 通过KEYS[1] 获取传入的key参数
- 通过ARGV[1]获取传入的limit参数
- redis.call方法,从缓存中get和key相关的值,如果为null那么就返回0
- 接着判断缓存中记录的数值是否会大于限制大小,如果超出表示该被限流,返回0
- 如果未超过,那么该key的缓存值+1,并设置过期时间为1秒钟以后,并返回缓存值+1
网关层限流
限流常在网关这一层做,比如Nginx、Openresty、Spring Cloud Gateway等,Spring Cloud Gateway网关限流底层实现原理,就是基于Redis + Lua,通过内置Lua限流脚本的方式实现的令牌桶。
Java编写 redis Lua 限流脚本:
/**
* @description 编写 redis Lua 限流脚本
*/
public String buildLuaScript() {
StringBuilder lua = new StringBuilder();
lua.append("local c");
lua.append("\nc = redis.call('get',KEYS[1])");
// 调用不超过最大值,则直接返回
lua.append("\nif c and tonumber(c) > tonumber(ARGV[1]) then");
lua.append("\nreturn c;");
lua.append("\nend");
// 执行计算器自加
lua.append("\nc = redis.call('incr',KEYS[1])");
lua.append("\nif tonumber(c) == 1 then");
// 从第一次调用开始限流,设置对应键值的过期
lua.append("\nredis.call('expire',KEYS[1],ARGV[2])");
lua.append("\nend");
lua.append("\nreturn c;");
return lua.toString();
}
更多推荐
已为社区贡献6条内容
所有评论(0)