/**
* lua脚本,计数器限流
* 5秒内限流10次
* @param key
* @return
*/
public boolean acquire(String key) {
long now = System.currentTimeMillis();
String KEY_PREFIX = "limiter_";
String QPS = "10"; //限流次数
Long time = 5000L; //时间
String ttl = "60";
key = KEY_PREFIX + key;
String oldest = String.valueOf(now - time);
String score = String.valueOf(now);
String scoreValue = score;
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setResultType(Long.class);
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("limiter2.lua")));
Long result = stringRedisTemplate.execute(redisScript, Arrays.asList(key), oldest, score, QPS, scoreValue, ttl);
return result.intValue() == 1;
}
lua脚本,limiter2.lua
--KEYS[1]: 限流 key
--ARGV[1]: 时间戳 - 时间窗口
--ARGV[2]: 当前时间戳(作为score)
--ARGV[3]: 阈值
--ARGV[4]: score 对应的唯一value
--ARGV[5]: 失效时间
-- 1. 移除时间窗口之前的数据
redis.call('zremrangeByScore', KEYS[1], 0, ARGV[1])
-- 2. 统计当前元素数量
local res = redis.call('zcard', KEYS[1])
-- 3. 是否超过阈值
if (res == nil) or (res < tonumber(ARGV[3])) then
redis.call('zadd', KEYS[1], ARGV[2], ARGV[4])
--设置过期时间,防止冷用户持续占用内存,
-- 过期时间应该是时间窗口长度+失效时间
redis.call("expire",KEYS[1],ARGV[5]);
return 1
else
return 0
end
在拦截其中,对需要限流的接口调用acquire方法