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_namelua_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,则对象将实际记录当前对象支持的共享内存区域中的事件;否则,它只是一个“试运行”(这是默认设置)。

返回值取决于以下情况

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

  1. 如果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 邮件列表供中文使用者使用。

错误和补丁

请通过以下方式报告错误或提交补丁:

  1. GitHub Issue Tracker 上创建工单,

  2. 或发布到 “OpenResty 社区”

作者

Monkey Zhang <timebug.info@gmail.com>,七牛云

版权和许可

此模块根据 BSD 许可证许可。

版权所有 (C) 2016-2017,由 Yichun “agentzh” Zhang,OpenResty Inc. 所有。

保留所有权利。

在满足以下条件的情况下,允许以源代码和二进制形式重新分发和使用,无论是否修改:

  • 源代码的重新分发必须保留上述版权声明、此条件列表和以下免责声明。

  • 二进制形式的重新分发必须在随分发提供的文档和/或其他材料中复制上述版权声明、此条件列表和以下免责声明。

本软件由版权持有人和贡献者“按原样”提供,并且任何明示或暗示的保证,包括但不限于适销性和适用于特定目的的暗示保证均被否认。在任何情况下,版权持有人或贡献者均不对任何直接、间接、偶然、特殊、惩罚性或后果性损害(包括但不限于替代商品或服务的采购;使用、数据或利润损失;或业务中断)负责,无论此类损害是基于合同、严格责任或侵权(包括疏忽或其他)理论,即使已告知存在此类损害的可能性。

另请参阅

作者

Monkey Zhang timebug.info@gmail.com

许可证

2bsd

版本