访问频次这里,常见的就是 IP
、用户 Token
。
IP
的话很容易误封,一般是同个 IP
多次频繁请求才会进行限制处理。
用户 Token
的话,只影响单个用户,也不会出现误封的情况。
这里只讨论用户 Token
的多级访问频次限制,这里主要采用 Redis Lua 脚本来实现。
Lua 脚本
local userKey = KEYS[1] -- 用户的 Key
local userStatus = false -- 是否触发频次限制,false 为未触发
for i = 1, #ARGV, 3 do -- 这里是遍历三个级别的频率限制,实际可以采用更多级的限制
local baseSec = tonumber(ARGV[i]) -- 基准时间段秒数
local maxNum = tonumber(ARGV[i+1]) -- 基准时间段内的最大请求数
local limitSec = tonumber(ARGV[i+2]) -- 触发后的限制秒数
-- 这里举个例子,比如 baseSec: 60,maxNum: 100,limitSec: 36000,代表 1 分钟内请求 100 次及以上则封禁 10 小时。
local reqCount = redis.call('INCR', userKey..":sc:"..baseSec) -- INCR此用户在这个基准时间段内的请求数的 key,获取一个最新值
if tonumber(reqCount) == 1 then
redis.call('EXPIRE', userKey..":sc:"..baseSec, baseSec) -- 如果没有 key 就设置一个
end
if tonumber(userCount) > maxReq then
-- 如果超过的话就直接把过期时间设置为限制时间,然后用户状态为触发限制
redis.call('EXPIRE', userKey..":sc:"..baseSec, limitSec)
userStatus = true
break
end
end
return userStatus
一个频率等级分为三个参数来控制,lua 参数传入时,按顺序三个参数是一组。
为了简化脚本执行,像是时间段的单位配置(比如 1d、2m 之类的),可以放在配置文件和代码中去解析完成。
这里需要注意的一点是,如果涉及到分片集群且没有代理的情况下,可能需要使用 Hash Tag
来对 Redis Key
进行统一标注,确保能分到一个分片上。
不过对于用户鉴权这种可以抽离成单点登录的,建议是一个单独的服务配一个单独的主从集群。