1,基于redis计数器
1)普通redis incr限流。
image.png不能保证原子性
2)lua脚本实现计数器限流。redis保证同时只有一条lua脚本执行,保证原子性。
执行lua脚本eval inc_rate_limit_script 3 query_data_limit_key 10 1000 1
image.png
2,基于令牌桶算法限流
1)lua脚本实现令牌桶限流
local key = KEYS[1] --作为锁定的key,hash类型
local max_permits = tonumber(KEYS[2]) --最大令牌数
local permits_per_second = tonumber(KEYS[3]) --每秒产生的令牌数
local req_permits = tonumber(ARGV[1]) --请求的令牌数
-- 下次请求可以获取令牌的开始时间
local next_free_ticket_micros = tonumber(redis.call('hget', key, 'next_free_ticket_micros') or 0)
local time = redis.call('time');
local now_micros = tonumber(time[1]) * 1000000 + tonumber(time[2])
-- 查询获取令牌是否超时
if(ARGV[2] ~= nil) then
--获取令牌的超时时间,第二个输入参数,微秒
local timeout_micros = tonumber(ARGV[2])
local micros_to_wait = next_free_ticket_micros - now_micros
if(micros_to_wait > timeout_micros) then
return micros_to_wait
end
end
-- 存储的令牌数
local stored_permis = tonumber(redis.call('hget', key, 'stored_permis') or 0)
--添加令牌的间隔微秒
local stable_interval = 1000000 / permits_per_second
-- 如果当前请求时间 大约保存的nextFreeTicketTime,则进行令牌补充
if(now_micros > next_free_ticket_micros) then
local new_permits = (now_micros - next_free_ticket_micros) / stable_interval
--更新存储的令牌数
stored_permis = math.min(max_permits, stored_permis + new_permits)
next_free_ticket_micros = now_micros
end
-- 获取令牌
local snapshot_time = next_free_ticket_micros
-- 本次可以花费的令牌
local permits_to_spend = math.min(req_permits, stored_permis)
-- 还差多少令牌不够
local fresh_permits = req_permits - permits_per_second
--需要等待的时间
local wait_micros = fresh_permits * stable_interval
redis.replicate_commands()--包装成MULTI/EXEC事务,发送给AOF或者从库
redis.call('hset', key, 'stored_permis', stored_permis - permits_to_spend)
redis.call('hset', key, 'next_free_ticket_micros', next_free_ticket_micros + wait_micros)
redis.call('expire', key, 10) --设置10s过期,有流量的情况下不会过期
return snapshot_time - now_micros;
2)调用逻辑
acquire获取:eval 'lua脚本' 3 '自定义的key' '最大存储的令牌数' '每秒钟产生的令牌数' '请求的令牌数'
eg:eval 'lua脚本' 3 my_limit_key 100 100 1
,返回线程需要等待的微秒数。
tryAcquire获取:eval 'lua脚本' 3 '自定义的key' '最大存储的令牌数' '每秒钟产生的令牌数' '请求的令牌数' '最大等待的微秒数'
eg:eval 'lua脚本' 3 my_limit_key 100 100 1 10000
返回线程需要等待的微秒,与req的timeout对比。
网友评论