lua-resty-limit-rate
用于 OpenResty/ngx_lua 限制请求速率的 Lua 模块,使用“令牌桶”方法
$ opm get upyun/lua-resty-limit-rate
名称
lua-resty-limit-rate - 用于 OpenResty/ngx_lua 限制请求速率的 Lua 模块,使用“令牌桶”方法。
概要
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, -- use lua-resty-lock
locks_shdict_name = "my_locks",
})
if not lim then
ngx.log(ngx.ERR,
"failed to instantiate a resty.limit.rate object: ", err)
return ngx.exit(500)
end
-- the following call must be per-request.
-- here we use the remote (IP) address as the limiting key
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, "failed to take token: ", err)
return ngx.exit(500)
end
if delay >= 0.001 then
-- the 2nd return value holds the current avail tokens number
-- of requests for the specified key
local avail = err
ngx.sleep(delay)
end
}
# content handler goes here. if it is content_by_lua, then you can
# merge the Lua code above in access_by_lua into your content_by_lua's
# Lua handler to save a little bit of CPU time.
}
location /take_available {
access_by_lua_block {
local limit_rate = require "resty.limit.rate"
-- global 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
-- single 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, "failed to take global: ", err)
return ngx.exit(500)
end
-- here we use the userid as the limiting key
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, "failed to take single: ", err)
return ngx.exit(500)
end
if t0 == 1 then
return -- global bucket is not hungry
else
if t1 == 1 then
return -- single bucket is not hungry
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"
返回。
此方法返回一个新的令牌桶,该令牌桶以每interval
quantum
个令牌的速度填充,直到给定的最大capacity
。该桶最初是满的。
此方法接受以下参数和一个可选的选项表opts
shdict_name
是 lua_shared_dict 共享内存区域的名称。最佳实践是为不同类型的限制器使用单独的共享内存区域。
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:incoming(key, commit)
触发一个新的请求传入事件,并计算针对指定密钥的当前请求所需的延迟(如果有)或用户是否应该立即拒绝它。
类似于 take 方法,但此方法每次仅从桶中获取一个令牌。
此方法接受以下参数
key
是用户指定的限制速率的密钥。请注意,此模块不会为用户密钥添加前缀或后缀,因此用户有责任确保密钥在
lua_shared_dict
共享内存区域中是唯一的。
commit
是一个布尔值。如果设置为true
,则对象将实际记录当前对象支持的共享内存区域中的事件;否则,它只是一个“试运行”(这是默认设置)。
set_max_wait
语法: obj:set_max_wait(max_wait?)
覆盖 new 方法中指定的max_wait
阈值。
take
语法: delay, err = obj:take(key, count, commit)
此方法从桶中获取 count 个令牌,不阻塞。
此方法接受以下参数
key
是用户指定的限制速率的密钥。请注意,此模块不会为用户密钥添加前缀或后缀,因此用户有责任确保密钥在
lua_shared_dict
共享内存区域中是唯一的。
count
是要移除的令牌数。
commit
是一个布尔值。如果设置为true
,则对象将实际记录当前对象支持的共享内存区域中的事件;否则,它只是一个“试运行”(这是默认设置)。
返回值取决于以下情况
如果在 new 或 set_max_wait 方法中指定了
max_wait
值,则此方法仅在令牌的等待时间不超过max_wait
时才从桶中获取令牌,并返回调用方应等待令牌实际可用之前的时间,否则返回nil
和错误字符串"rejected"
。
如果
max_wait
值为 nil,则返回调用方应等待令牌实际可用之前的时间。
此外,此方法还返回第二个返回值,指示此时当前可用令牌的数量。
如果发生错误(例如访问当前对象支持的lua_shared_dict
共享内存区域时出现故障),则此方法将返回 nil 和一个描述错误的字符串。
此方法本身永远不会休眠。它仅在必要时返回延迟,并要求调用方稍后调用 ngx.sleep 方法来休眠。
take_available
语法: count, err = obj:take_available(key, count)
此方法最多从桶中获取 count 个立即可用的令牌。它返回移除的令牌数,如果没有任何可用令牌,则返回零。它不会阻塞。
此方法接受以下参数
key
是用户指定的限制速率的密钥。请注意,此模块不会为用户密钥添加前缀或后缀,因此用户有责任确保密钥在
lua_shared_dict
共享内存区域中是唯一的。
count
是要移除的令牌数。
如果发生错误(例如访问当前对象支持的 lua_shared_dict 共享内存区域时出现故障),则此方法将返回 nil 和一个描述错误的字符串。
uncommit
语法: ok, err = obj:uncommit(key)
这会尝试撤消incoming
调用的提交。这仅仅是一个近似值,应谨慎使用。此方法主要用于在 resty.limit.traffic Lua 模块中同时组合多个限制器时使用。
限制粒度
限制作用于单个 NGINX 服务器实例(包括其所有工作进程)的粒度。由于共享内存机制;我们可以廉价地在单个 NGINX 服务器实例中的所有工作进程之间共享状态。
安装
请参阅 库安装说明。
社区
英文邮件列表
The openresty-en 邮件列表供英语使用者使用。
中文邮件列表
The openresty 邮件列表供中文使用者使用。
错误和补丁
请通过以下方式报告错误或提交补丁:
在 GitHub Issue Tracker 上创建工单,
或发布到 “OpenResty 社区”。
作者
Monkey Zhang <timebug.info@gmail.com>,七牛云
版权和许可
此模块根据 BSD 许可证许可。
版权所有 (C) 2016-2017,由 Yichun “agentzh” Zhang,OpenResty Inc. 所有。
保留所有权利。
在满足以下条件的情况下,允许以源代码和二进制形式重新分发和使用,无论是否修改:
源代码的重新分发必须保留上述版权声明、此条件列表和以下免责声明。
二进制形式的重新分发必须在随分发提供的文档和/或其他材料中复制上述版权声明、此条件列表和以下免责声明。
本软件由版权持有人和贡献者“按原样”提供,并且任何明示或暗示的保证,包括但不限于适销性和适用于特定目的的暗示保证均被否认。在任何情况下,版权持有人或贡献者均不对任何直接、间接、偶然、特殊、惩罚性或后果性损害(包括但不限于替代商品或服务的采购;使用、数据或利润损失;或业务中断)负责,无论此类损害是基于合同、严格责任或侵权(包括疏忽或其他)理论,即使已告知存在此类损害的可能性。
另请参阅
ngx_lua 模块:https://github.com/openresty/lua-nginx-module
OpenResty:https://openresty.org.cn/
作者
Monkey Zhang timebug.info@gmail.com
许可证
2bsd
版本
-
用于 OpenResty/ngx_lua 限制请求速率的 Lua 模块,使用“令牌桶”方法 2021-06-11 13:04:54