limit-rate: 用于限制 nginx-module-lua 请求速率的 Lua 模块,使用“令牌桶”方法
安装
如果您尚未设置 RPM 存储库订阅,请 注册。然后您可以继续以下步骤。
CentOS/RHEL 7 或 Amazon Linux 2
yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install lua-resty-limit-rate
CentOS/RHEL 8+、Fedora Linux、Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-limit-rate
要在 NGINX 中使用此 Lua 库,请确保已安装 nginx-module-lua。
本文档描述了 lua-resty-limit-rate v0.1,于 2018 年 10 月 25 日发布。
http {
lua_shared_dict my_limit_rate_store 100m;
lua_shared_dict my_locks 100k;
server {
location / {
access_by_lua_block {
local limit_rate = require "resty.limit.rate"
local lim, err = limit_rate.new("my_limit_rate_store", 500, 10, 3, 200, {
lock_enable = true, -- 使用 lua-resty-lock
locks_shdict_name = "my_locks",
})
if not lim then
ngx.log(ngx.ERR,
"实例化 resty.limit.rate 对象失败: ", err)
return ngx.exit(500)
end
-- 以下调用必须是每个请求。
-- 这里我们使用远程 (IP) 地址作为限制键
local key = ngx.var.binary_remote_addr
local delay, err = lim:incoming(key, true)
-- local delay, err = lim:take(key, 1, ture)
if not delay then
if err == "rejected" then
return ngx.exit(503)
end
ngx.log(ngx.ERR, "获取令牌失败: ", err)
return ngx.exit(500)
end
if delay >= 0.001 then
-- 第二个返回值保存指定键的当前可用令牌数量
local avail = err
ngx.sleep(delay)
end
}
# 内容处理程序在此处。如果是 content_by_lua,则可以
# 将上面的 Lua 代码合并到 content_by_lua 的
# Lua 处理程序中,以节省一些 CPU 时间。
}
location /take_available {
access_by_lua_block {
local limit_rate = require "resty.limit.rate"
-- 全局 20r/s 6000r/5m
local lim_global = limit_rate.new("my_limit_rate_store", 100, 6000, 2, nil, {
lock_enable = true,
locks_shdict_name = "my_locks",
})
if not lim_global then
return ngx.exit(500)
end
-- 单个 2r/s 600r/5m
local lim_single = limit_rate.new("my_limit_rate_store", 500, 600, 1, nil, {
locks_shdict_name = "my_locks",
})
if not lim_single then
return ngx.exit(500)
end
local t0, err = lim_global:take_available("__global__", 1)
if not t0 then
ngx.log(ngx.ERR, "获取全局令牌失败: ", err)
return ngx.exit(500)
end
-- 这里我们使用用户 ID 作为限制键
local key = ngx.var.arg_userid or "__single__"
local t1, err = lim_single:take_available(key, 1)
if not t1 then
ngx.log(ngx.ERR, "获取单个令牌失败: ", err)
return ngx.exit(500)
end
if t0 == 1 then
return -- 全局桶不缺令牌
else
if t1 == 1 then
return -- 单个桶不缺令牌
else
return ngx.exit(503)
end
end
}
}
}
}
描述
该模块提供 API 以帮助 OpenResty/ngx_lua 用户程序员使用“令牌桶”方法限制请求速率。
如果您想同时使用多个不同实例的此类,或将此类的一个实例与其他类的实例(如 resty.limit.conn)一起使用,则 必须 使用 resty.limit.traffic 模块将它们组合在一起。
该模块与 resty.limit.req 之间的主要区别:
- resty.limit.req 使用“漏桶”方法限制请求速率,而该模块使用“令牌桶”方法。
该模块与 resty.limit.count 之间的主要区别:
- resty.limit.count 提供了一个简单的思维模型,通过在给定时间窗口内限制固定数量的请求速率,但有时可能会允许每分钟两倍的请求数量。例如,如果我们的速率限制为每分钟 10 个请求,而用户在 10:00:59 发出了 10 个请求,他们可以在 10:01:00 再发出 10 个请求,因为每分钟开始时会重新计数。在这种情况下,该模块能够更精确和平滑地控制。
方法
new
语法: obj, err = class.new(shdict_name, interval, capacity, quantum?, max_wait?, opts?)
实例化此类的对象。class 值由调用 require "resty.limit.rate" 返回。
该方法返回一个新的令牌桶,以 quantum 数量的令牌每 interval 填充,最多达到给定的最大 capacity。桶最初是满的。
该方法接受以下参数和一个可选的选项表 opts:
-
shdict_name是 lua_shared_dict shm 区域的名称。最佳实践是为不同类型的限制器使用单独的 shm 区域。
-
interval是添加令牌之间的时间,单位为毫秒。 -
capacity是桶中可以容纳的最大令牌数量。 -
quantum是在一个时间间隔内添加到桶中的令牌数量,此参数是可选的,默认值为1。 -
max_wait是我们等待足够令牌添加的最长时间,单位为毫秒,此参数是可选的,默认值为nil,表示无限。
选项表接受以下选项:
-
lock_enable启用时,跨多个 nginx 工作进程更新 shdict 状态是原子的;否则在“读取然后写入”行为之间会有一个(小)竞争条件窗口,默认值为false。有关更多详细信息,请参见 lua-resty-lock。 -
locks_shdict_name指定用于锁的共享字典名称(由 lua_shared_dict 创建),默认值为locks。
如果失败,该方法返回 nil 和描述错误的字符串(如错误的 lua_shared_dict 名称)。
incoming
语法: delay, err = obj:take(key, commit)
触发新的请求到达事件,并计算当前请求在指定键上所需的延迟(如果有)或用户是否应立即拒绝它。
类似于 take 方法,但该方法一次只从桶中取出一个令牌。
该方法接受以下参数:
-
key是用户指定的限制速率的键。请注意,该模块不对用户键进行前缀或后缀处理,因此确保键在
lua_shared_dictshm 区域中唯一是用户的责任。 -
commit是一个布尔值。如果设置为true,对象将实际记录当前对象在 shm 区域中的事件;否则,它只是一次“干运行”(这是默认值)。
set_max_wait
语法: obj:set_max_wait(max_wait?)
覆盖 new 方法中指定的 max_wait 阈值。
take
语法: delay, err = obj:take(key, count, commit)
该方法从桶中取出 count 个令牌而不阻塞。
该方法接受以下参数:
-
key是用户指定的限制速率的键。请注意,该模块不对用户键进行前缀或后缀处理,因此确保键在
lua_shared_dictshm 区域中唯一是用户的责任。 -
count是要移除的令牌数量。 -
commit是一个布尔值。如果设置为true,对象将实际记录当前对象在 shm 区域中的事件;否则,它只是一次“干运行”(这是默认值)。
返回值取决于以下情况:
-
如果在 new 或 set_max_wait 方法中指定的
max_wait值,该方法将仅在令牌的等待时间不超过max_wait时从桶中取出令牌,并返回调用者应等待的时间,直到令牌实际可用,否则返回nil和错误字符串"rejected"。 -
如果
max_wait值为 nil,则返回调用者应等待的时间,直到令牌实际可用。
此外,该方法还返回第二个返回值,指示此时当前可用令牌的数量。
如果发生错误(例如访问当前对象的 lua_shared_dict shm 区域时失败),则该方法返回 nil 和描述错误的字符串。
该方法本身不会休眠。它仅在必要时返回延迟,并要求调用者稍后调用 ngx.sleep 方法进行休眠。
take_available
语法: count, err = obj:take_available(key, count)
该方法从桶中立即取出最多 count 个可用令牌。它返回移除的令牌数量,如果没有可用令牌,则返回零。它不会阻塞。
该方法接受以下参数:
-
key是用户指定的限制速率的键。请注意,该模块不对用户键进行前缀或后缀处理,因此确保键在
lua_shared_dictshm 区域中唯一是用户的责任。 -
count是要移除的令牌数量。
如果发生错误(例如访问当前对象的 lua_shared_dict shm 区域时失败),则该方法返回 nil 和描述错误的字符串。
uncommit
语法: ok, err = obj:uncommit(key)
这尝试撤销 incoming 调用的提交。这只是一个近似值,应该谨慎使用。该方法主要用于在同时组合多个限制器时使用 resty.limit.traffic Lua 模块。
限制粒度
限制在单个 NGINX 服务器实例(包括所有工作进程)的粒度上工作。由于 shm 机制,我们可以在单个 NGINX 服务器实例中的所有工作进程之间廉价地共享状态。
另见
- 模块 resty.limit.req
- 模块 resty.limit.conn
- 模块 resty.limit.count
- 模块 resty.limit.traffic
- 库 lua-resty-limit-traffic
- ngx_lua 模块: https://github.com/openresty/lua-nginx-module
- OpenResty: https://openresty.org/
GitHub
您可以在 nginx-module-limit-rate 的 GitHub 存储库 中找到此模块的其他配置提示和文档。