流程图
代码实现
/**
* 是否限制接口请求
*
* @param request 请求体
* @return true-禁止请求 false-允许请求
*/
private boolean limitApi(HttpServletRequest request) {
String uri = request.getRequestURI();
String ip = request.getRemoteAddr();
String key = "limit:" + uri + ":" + ip;
// 30秒内限制10次访问
long limit = 10L;
long timeout = 30L;
boolean b = RedisUtils.setNX(key, 1, timeout, TimeUnit.SECONDS);
if (b) {
log.info("1时间范围内第一次访问,允许");
return false;
} else {
Object result = RedisUtils.getString(key);
if (result == null) {
RedisUtils.setNX(key, 1, timeout, TimeUnit.SECONDS);
log.info("2时间范围内第一次访问,允许");
return false;
} else {
long count = Long.parseLong(String.valueOf(result));
if (count >= limit) {
log.error("时间范围内请求次数过多, 已经请求次数:{},不允许访问", count + 1);
return true;
} else {
RedisUtils.increment(key);
log.info("请求次数符合限制, 已经请求次数:{},允许", count + 1);
return false;
}
}
}
}
注意点
- Redis
increment
操作要使用StringRedisTemplate
,否则会出现操作异常; - 最好使用
Lua
脚本操作,避免 Redis 的多次连接,保证原子性和性能;
附:Lua脚本
把限制逻辑封装到一个Lua脚本中,调用时只需传入:key、限制数量、过期时间,调用结果就会指明是否运行访问
local notexists = redis.call(\"set\", KEYS[1], 1, \"NX\", \"EX\", tonumber(ARGV[2]))
if (notexists) then
return 1
end
local current = tonumber(redis.call(\"get\", KEYS[1]))
if (current == nil) then
local result = redis.call(\"incr\", KEYS[1])
redis.call(\"expire\", KEYS[1], tonumber(ARGV[2]))
return result
end
if (current >= tonumber(ARGV[1])) then
error(\"too many requests\")
end
local result = redis.call(\"incr\", KEYS[1])
return result
使用 eval 调用
eval 脚本 1 key 参数-允许的最大次数 参数-过期时间
参考:https://www.cnblogs.com/duhuo/p/5002319.html