APISIX源码解析-插件-固定窗口限速【limit-count】

limit-count 固定窗口限速插件

关键属性

在这里插入图片描述

源码实现

local function create_limit_obj(conf)
    core.log.info("create new limit-count plugin instance")

    if not conf.policy or conf.policy == "local" then
        return limit_local_new("plugin-" .. plugin_name, conf.count,
                               conf.time_window)
    end

    if conf.policy == "redis" then
        return limit_redis_new("plugin-" .. plugin_name,
                               conf.count, conf.time_window, conf)
    end

    if conf.policy == "redis-cluster" then
        return limit_redis_cluster_new("plugin-" .. plugin_name, conf.count,
                                       conf.time_window, conf)
    end

    return nil
end


function _M.access(conf, ctx)
    core.log.info("ver: ", ctx.conf_version)
    local lim, err = core.lrucache.plugin_ctx(lrucache, ctx, conf.policy, create_limit_obj, conf)
    if not lim then
        core.log.error("failed to fetch limit.count object: ", err)
        if conf.allow_degradation then
            return
        end
        return 500
    end

    local key = (ctx.var[conf.key] or "") .. ctx.conf_type .. ctx.conf_version
    core.log.info("limit key: ", key)

    local delay, remaining = lim:incoming(key, true)
    if not delay then
        local err = remaining
        if err == "rejected" then
            if conf.rejected_msg then
                return conf.rejected_code, { error_msg = conf.rejected_msg }
            end
            return conf.rejected_code
        end

        core.log.error("failed to limit count: ", err)
        if conf.allow_degradation then
            return
        end
        return 500, {error_msg = "failed to limit count"}
    end

    if conf.show_limit_quota_header then
        core.response.set_header("X-RateLimit-Limit", conf.count,
            "X-RateLimit-Remaining", remaining)
    end
end

使用Openresty中的resty.limit.count模块

resty.limit.count 模块就是限制接口单位时间的请求数,

return limit_local_new("plugin-" .. plugin_name, conf.count,
                               conf.time_window)

该 new 方法携带的参数如下:
shdict_name: lua_shared_dict 声明的共享内存的名称。建议对不同的限制使用独立的共享内存。
count:指定的请求阈值。
time_window: 请求个数复位前的窗口时间,以秒为单位。

local delay, remaining = lim:incoming(key, true)

key: 是用户指定限制速率的 key。
commit:布尔值。如果设置为 true,则 lim将会在支持该 lim的共享内存中记录该事件;否则仅仅是 “dry run”。

返回值:

1、如果请求数没有超过在 new 方法中设置的 count 值,那么该 incoming 返回 0 作为
delay,remaining表示当前时间内余下允许请求的个数。
2、如果请求数超过了 count 限制,则返回 nil 和错误字符串 “rejected”。
3、如果发生错误(如访问共享内存失败),则该方法返回 nil 和错误描述字符串。

    if conf.show_limit_quota_header then
        core.response.set_header("X-RateLimit-Limit", conf.count,
            "X-RateLimit-Remaining", remaining)
    end

是否在响应头中显示 X-RateLimit-Limit 和 X-RateLimit-Remaining (限制的总请求数和剩余还可以发送的请求数),默认值是 true。

    if conf.policy == "redis" then
        return limit_redis_new("plugin-" .. plugin_name,
                               conf.count, conf.time_window, conf)
    end

    if conf.policy == "redis-cluster" then
        return limit_redis_cluster_new("plugin-" .. plugin_name, conf.count,
                                       conf.time_window, conf)
    end

同时支持通过redis或redis集群方式进行记录和限制,使用redis eval命令。
redis Eval 命令基本语法如下:
EVAL script numkeys key [key …] arg [arg …]
参数说明:
1、script: 参数是一段 Lua 5.1 脚本程序。脚本不必(也不应该)定义为一个 Lua 函数。
2、numkeys: 用于指定键名参数的个数。
3、key [key …]: 从 EVAL 的第三个参数开始算起,表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。
4、arg [arg …]: 附加参数,在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)。

function _M.incoming(self, key)
    ...
    local remaining, err = red:eval(script, 1, key, limit, window)
    ...
    
local script = [=[
    if redis.call('ttl', KEYS[1]) < 0 then
        redis.call('set', KEYS[1], ARGV[1] - 1, 'EX', ARGV[2])
        return ARGV[1] - 1
    end
    return redis.call('incrby', KEYS[1], -1)
]=]

Redis命令操作
call :
脚本命令执行
ttl
当 key 不存在时,返回 -2 。 当 key 存在但没有设置剩余生存时间时,返回 -1 。 否则,以秒为单位,返回 key 的剩余生存时间。
注意:在 Redis 2.8 以前,当 key 不存在,或者 key 没有设置剩余生存时间时,命令都返回 -1 。
set ex:
设置键值的同时,设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
Incrby :
将 key 中储存的数字加上指定的增量值。
如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCRBY 命令。
如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
操作的值限制在 64 位(bit)有符号数字表示之内。

综上,Redis实现方式为:

1、当key不存在时,第一次访问,设置key : limit-1,key的过期时间为window,通过key过期机制,实现指定窗口时间内的计数重置
2、当key存在时,每次访问自动-1,代表剩余个数-1
3、当剩余个数 < 0 时,返回"rejected",代表请求拒绝

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值