业务场景为限制消息发送,要求每天不超过一次,每七天不超过三次。
Redission 的 RRateLimiter 虽然功能完备且支持自定义限流配置,但是每个限流器都需要维护三个 key,并且 lua 脚本中的判断逻辑较为复杂。
见:Redisson 分布式限流器 RRateLimiter 的使用及原理
此外,本业务场景每次固定只需要获取一个令牌,且时间等限流参数固定,因此完全可以通过一个 sorted set 实现令牌桶限流。
对应 lua 脚本:
-- sorted set 令牌桶的 key
local key = KEYS[1];
-- 当前日期 格式为 yyyy-MM-dd
local member = ARGV[1];
-- 当前日期 0 点对应的时间戳 单位为秒
local timestamp = tonumber(ARGV[2]);
local exists = redis.call('exists', key);
if exists == 0 then
-- 创建并授权
redis.call('zadd', key, timestamp, member);
redis.call('expire', key, 7 * 24 * 60 * 60);
return 1;
else
-- 移除七天前的授权记录 本质是回收令牌
local sevenDaysAgo = timestamp - 7 * 24 * 60 * 60;
redis.call('zremrangebyscore', key, '-inf', sevenDaysAgo);
-- 如果队列长度少于 3 则尝试进行授权
local length = redis.call('zcard', key);
if length < 3 then
local count = redis.call('zcount', key, timestamp, timestamp);
if (count == 0) then
-- 每天不超过一次
redis.call('zadd', key, timestamp, member);
redis.call('expire', key, 7 * 24 * 60 * 60);
return 1;
else
return 0;
end;
else
return 0;
end;
end;