分布式限流

分布式限流最关键的是要将限流服务做成原子化,而解决方案可以使用Redis+Lua或者Nginx+Lua技术来实现。

限制某个接口的时间窗请求数

Redis+Lua实现

我们需要编写一个Lua脚本:

-- 限流脚本
local key = KEY[1] -- 限流KEY(1秒1个)
local limite = tonumber(ARGV[1]) -- 限流大小
local current = tonumber(redis.call("INCRBY",key, "1")) -- 请求数加1
if current > limite then 
    return 0
elseif current == 1 then
    redis.call("EXPIRE", key, "2")
end
return 1

脚本说明(Lua小白必备)

①在Lua脚本中执行Redis命令:redis.call()

tonumber是Lua的一个函数,这个函数会尝试将它的参数转换为数字

③ARGV[1]表示传递给脚本的第一个参数的值

因为如上操作是在一个Lua脚本中,且Redis是单线程模型,因此是线程安全的。但是上面的脚本有一个问题:当达到限流大小后还是会递增,可以进行如下改造:

写好lua脚本之后,在Java代码中判断是否需要限流:

从 Redis 2.6.0 版本开始,通过内置的 Lua 解释器,可以使用 EVAL 命令对 Lua 脚本进行求值。

因为Redis的限制,不能在Redis Lua中使用TIME获取时间戳,因此只能通过应用传入。在某些情况下(机器时钟不准),限流会存在一些问题。

仔细阅读上述方案,我们不难发现,其实这就是应用级限流中“限制某个接口的时间窗请求数”的翻版,这里将之前的代码贴出来:

Redis+Lua方案中,Redis其实充当的是缓存角色,Lua脚本只是为了保证判断流程的原子性,与应用内的“限制某个接口的时间窗请求数”的思路是一致的。

Nginx+Lua实现

在上面的脚本中,我们使用了lua-resty-lock互斥锁模块来解决原子问题,并使用ngx.shared.DICT共享字典来实现计数器,所以需要在Nginx的配置中先定义两个共享字典(分别用来存放锁和计数器):

总结

有人会纠结:如果应用并发量非常大,Redis或者Nginx是否能扛得住?这个问题要从多方面来考虑:流量是不是真的有这么大,是不是当并发量太大时降级为应用级限流。京东目前的抢购业务就是使用Redis+Lua来限流的,详见京东抢购服务高并发实践

对于分布式限流,一般都是业务场景需要这种形式的限流;而流量入口的限流则应该在接入层来完成。

内容来源:

《亿级流量网站架构核心技术》:限流详解

Last updated