redis结合lua脚本的几个实例

445 阅读2分钟

本文已参与[新人创作礼]活动,一起开启掘金创作之路。

redis+lua 限流

  • 计数器模式

-- 获取调用脚本时传入的第一个key值(用作限流的 key)
local key = KEYS[1]
-- 获取调用脚本时传入的第一个参数值(限流大小)
local limit = tonumber(ARGV[1])

-- 获取当前流量大小
local curentLimit = tonumber(redis.call('get', key) or "0")

-- 是否超出限流
if curentLimit + 1 > limit then
    -- 返回(拒绝)
    return 0
else
    -- 没有超出 value + 1
    redis.call("INCRBY", key, 1)
    -- 设置过期时间
    redis.call("EXPIRE", key, 2)
    -- 返回(放行)
    return 1
end

计数器模式缺点:粒度不够细的情况下, 会出现在同一个窗口时间内出现双倍请求数,例如设置100/s的限流,如果0.9秒时有90个请求,1.1秒时有80个,那么0.5s到1.5s这一秒的窗口时间就有170个请求。

  • 令牌桶模式

每次访问从桶中取令牌,有令牌才允许通过,一段时间后放回,根据桶中令牌数量和令牌恢复的时间来控制流量


--last_mill_second 最后加入时间(毫秒)
--curr_permits 当前可用的令牌
--max_burst 令牌桶最大值
--rate 每秒生成几个令牌
--app 应用
--ARGV[1] 当前时间
--ARGV[2] 需取走令牌数量

if KEYS[1] == nil then
    return -1
end

if(redis.pcall("EXISTS",KEYS[1])==0) 
then
--初始化
redis.pcall("HMSET",KEYS[1],
        "last_mill_second",ARGV[1],
        "curr_permits",ARGV[2],
        "max_burst",ARGV[3],
        "rate",ARGV[4],
        "app",ARGV[5])
end 
local ratelimit_info=redis.pcall("HMGET",KEYS[1],"last_mill_second","curr_permits","max_burst","rate","app")
local last_mill_second=ratelimit_info[1]
local curr_permits=tonumber(ratelimit_info[2])
local max_burst=tonumber(ratelimit_info[3])
local rate=tonumber(ratelimit_info[4])
local app=tostring(ratelimit_info[5])


local local_curr_permits=max_burst;

if(type(last_mill_second) ~='boolean' and last_mill_second ~=nil) then
	--计算可以加入的最大令牌
    local reverse_permits=math.floor((ARGV[1]-last_mill_second)/1000)*rate
    if(reverse_permits>0) then
		--如果可以加入,则把当前时间作为最后加入令牌时间
        redis.pcall("HMSET",KEYS[1],"last_mill_second",ARGV[1])
    end
	--计算加入令牌后最大值
	--防止节点转发出现时间早的后出现,保证传输顺序
	reverse_permits=math.max(reverse_permits,0);
    local expect_curr_permits=reverse_permits+curr_permits
	--超出最大容量则令牌溢出 丢弃
    local_curr_permits=math.min(expect_curr_permits,max_burst);

else
    redis.pcall("HMSET",KEYS[1],"last_mill_second",ARGV[1])
end

local result=-1
if(local_curr_permits-ARGV[2]>0) then
    result=1
	--如果可以获取令牌,则去掉此时需要拿走的令牌
    redis.pcall("HMSET",KEYS[1],"curr_permits",local_curr_permits-ARGV[2])
else
	--如果不行,则把当前最新的令牌数写入
    redis.pcall("HMSET",KEYS[1],"curr_permits",local_curr_permits)
end

return result