跳转至

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_namelua_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_dict shm 区域中唯一是用户的责任。

  • 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_dict shm 区域中唯一是用户的责任。

  • count 是要移除的令牌数量。

  • commit 是一个布尔值。如果设置为 true,对象将实际记录当前对象在 shm 区域中的事件;否则,它只是一次“干运行”(这是默认值)。

返回值取决于以下情况:

  1. 如果在 newset_max_wait 方法中指定的 max_wait 值,该方法将仅在令牌的等待时间不超过 max_wait 时从桶中取出令牌,并返回调用者应等待的时间,直到令牌实际可用,否则返回 nil 和错误字符串 "rejected"

  2. 如果 max_wait 值为 nil,则返回调用者应等待的时间,直到令牌实际可用。

此外,该方法还返回第二个返回值,指示此时当前可用令牌的数量。

如果发生错误(例如访问当前对象的 lua_shared_dict shm 区域时失败),则该方法返回 nil 和描述错误的字符串。

该方法本身不会休眠。它仅在必要时返回延迟,并要求调用者稍后调用 ngx.sleep 方法进行休眠。

take_available

语法: count, err = obj:take_available(key, count)

该方法从桶中立即取出最多 count 个可用令牌。它返回移除的令牌数量,如果没有可用令牌,则返回零。它不会阻塞。

该方法接受以下参数:

  • key 是用户指定的限制速率的键。

    请注意,该模块不对用户键进行前缀或后缀处理,因此确保键在 lua_shared_dict shm 区域中唯一是用户的责任。

  • count 是要移除的令牌数量。

如果发生错误(例如访问当前对象的 lua_shared_dict shm 区域时失败),则该方法返回 nil 和描述错误的字符串。

uncommit

语法: ok, err = obj:uncommit(key)

这尝试撤销 incoming 调用的提交。这只是一个近似值,应该谨慎使用。该方法主要用于在同时组合多个限制器时使用 resty.limit.traffic Lua 模块。

限制粒度

限制在单个 NGINX 服务器实例(包括所有工作进程)的粒度上工作。由于 shm 机制,我们可以在单个 NGINX 服务器实例中的所有工作进程之间廉价地共享状态。

另见

GitHub

您可以在 nginx-module-limit-rate 的 GitHub 存储库 中找到此模块的其他配置提示和文档。