1:背景
在高并发业务场景下,常用的三板斧:"熔断、降级和限流"。接下来重点梳理一下常用的限流算法的几种实现方式。
2:常用解决方案
1:漏桶算法
首先,我们有一个固定容量的桶,有水流进来,也有水流出去。对于流进来的水来说,我们无法预计一共有多少水会流进来,也无法预计水流的速度。但是对于流出去的水来说,这个桶可以固定水流出的速率。而且,当桶满了之后,多余的水将会溢出。
我们将算法中的水换成实际应用中的请求,我们可以看到漏桶算法天生就限制了请求的速度。当使用了漏桶算法,我们可以保证接口会以一个常速速率来处理请求。所以漏桶算法天生不会出现临界问题。
漏桶算法可以粗略的认为就是注水漏水过程,往桶中以一定速率流出水,以任意速率流入水,当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。
2:令牌桶算法
令牌桶算法是一种常用的限流算法,可以用于限制单位时间内的请求的数量。 该算法维护一个固定容量的令牌桶,每秒钟会向令牌桶放入一定数量的令牌。 当有请求到来时,如果令牌桶中有足够的令牌,则请求被允许通过并从令牌桶中消耗一个令牌,否则请求被拒绝。
3:redis& lua (滑动窗口限流)
Lua脚本和 MySQL数据库的存储过程比较相似,他们执行一组命令,所有命令的执行要么全部成功或者失败,以此达到原子性。也可以把Lua脚本理解为,一段具有业务逻辑的代码块。
-- 获取调用脚本时传入的第一个key值(用作限流的 key)
local c
c = redis.call('get',KEYS[1])
// 调用不超过最大值,则直接返回
if c and tonumber(c) > tonumber(ARGV[1]) then
return c;
end
// 执行计算器自加
c = redis.call('incr',KEYS[1])
if tonumber(c) == 1 then
// 从第一次调用开始限流,设置对应键值的过期
redis.call('expire',KEYS[1],ARGV[2])
end
return c;
KEYS[1] 用来表示在redis 中用作键值的参数占位,主要用來传递在redis 中用作keyz值的参数。
ARGV[1] 用来表示在redis 中用作参数的占位,主要用来传递在redis中用做 value值的参数。
3: 总结
限流常在网关这一层做,比如Nginx、Openresty、Kong、Zuul、Spring Cloud Gateway等,而像spring cloud - gateway网关限流底层实现原理,就是基于Redis + Lua,通过内置Lua限流脚本的方式。