All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.edgar615.util.redis.lua.multi_token_bucket.lua Maven / Gradle / Ivy

The newest version!
local rate_limits = cjson.decode(ARGV[1])
local now = tonumber(ARGV[2])
local tokens_to_take       = tonumber(ARGV[3]) or 1 --当前请求需要的令牌数量

--local prefix_key = "token.bucket." --限流KEY
local result = {}
local passed = true;
--计算请求是否满足每个限流规则
for i, rate_limit in ipairs(rate_limits) do
    local subject = "token.bucket." .. rate_limit[1] --桶的标识符
    local burst = math.max(tonumber(rate_limit[2]), 1) --桶中最大令牌数,最小值1,
    local refillTime = tonumber(rate_limit[3]) or 1000-- 向桶中添加令牌的周期,单位毫秒
    local refillAmount = math.max(tonumber(rate_limit[4]), 1) or 1 -- 每次refillTime向桶中添加的令牌数量,默认为1

    local available_tokens = burst --可用的令牌数默认等于桶的容量
    local last_update = now --第一次的last_update等于当前时间
    local current = redis.call('HMGET', subject, 'last_update', 'available_tokens')
    if current.err ~= nil then
        redis.call('DEL', subject)
        current = {}
        redis.log(redis.LOG_NOTICE, 'Cannot get ratelimit ' .. subject)
        return redis.error_reply('Cannot get ratelimit ' .. rate_limit[1])
    end

    --计算从上次的时间戳与当前时间戳计算应该添加的令牌数
    if current[1] then
        --上次请求的时间
        last_update = current[1]
        local content = current[2]
        --计算应该生成的令牌数
        local delta_ms = math.max(now - last_update, 0)
        local refillCount  = math.floor(delta_ms / refillTime) * refillAmount
        --如果桶满,直接使用桶的容量
        available_tokens = math.min(content + refillCount, burst)
    end
    if available_tokens < tokens_to_take then
        passed = false
        table.insert(result, { subject, burst, refillTime, refillAmount, available_tokens, 0, last_update})
    else
        table.insert(result, { subject, burst, refillTime, refillAmount, available_tokens, 1, last_update})
    end
end

-- 如果通过,增加每个限流规则
if passed == true then
    for key,value in ipairs(result) do
        value[5] = math.min( value[5] - tokens_to_take,  value[2])
        value[7]  =  value[7]  + math.floor(tokens_to_take /  value[4]  *  value[3] )

        --重新设置令牌
        redis.call('HMSET', value[1],
            'last_update', value[7],
            'available_tokens', value[5])

        --如果没有新的请求过来,在桶满之后可以直接将该令牌删除。
        redis.call('PEXPIRE', value[1], math.ceil((value[2] /  value[4]) * value[3]))
    end
end

-- 返回结果
local summary = {}
for key,value in ipairs(result) do
    local pass = value[6]
    local reset_time = value[7] - now
    if reset_time < 0 then
        reset_time = now - value[7]
    end
    if not pass or pass == 0 then
        --添加四个值 是否通过0或1 剩余请求数 最大请求数 重置时间
        table.insert(summary, 0)
        table.insert(summary,  math.max(value[5], 0))
        table.insert(summary, value[2])
        table.insert(summary, reset_time)
    else
        table.insert(summary, 1)
        table.insert(summary, math.max(value[5], 0))
        table.insert(summary, value[2])
        table.insert(summary, reset_time)
    end
end

return summary




© 2015 - 2025 Weber Informatics LLC | Privacy Policy