参见:
https://blog.csdn.net/forezp/article/details/85081162
https://cloud.spring.io/spring-cloud-gateway/2.2.x/reference/html/#the-requestratelimiter-gatewayfilter-factory
http://www.ityouknow.com/springcloud/2019/01/26/spring-cloud-gateway-limit.html
https://www.cnblogs.com/sea520/p/11541789.html
https://blog.csdn.net/wxxiangge/article/details/95024214?utm_medium=distribute.pc_relevant.none-task-blog-baidujs-6
lua脚本
参见spring-spring-cloud-gateway-core包下的request_rate_limiter.lua
Redis从2.6版本开始引入对Lua脚本的支持,通过在服务器中嵌入Lua环境,Redis客户端可以使用Lua脚本,直接在服务器端原子地执行多个Redis命令
@Bean
@Primary
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getHostName());
}
对比之后的注解过程:
-- redis中的值,只有一次访问时候
-- request_rate_limiter.{localhost}.tokens 令牌桶
local tokens_key = KEYS[1]
-- request_rate_limiter.{localhost}.timestamp 时间戳
local timestamp_key = KEYS[2]
-- 通过截图可知
local rate = tonumber(ARGV[1]) -- = 10 允许用户每秒处理多少个请求
local capacity = tonumber(ARGV[2])-- = 20 令牌桶的容量,允许在一秒钟内完成的最大请求数
local now = tonumber(ARGV[3])-- = 159173167 Instant.now().getEpochSecond()当前时间戳
local requested = tonumber(ARGV[4]) -- = 访问量 1,浏览器模拟一个请求
local fill_time = capacity/rate -- 20/10 = 2
local ttl = math.floor(fill_time*2) -- 过期时间4秒
-- 获取redis上一次token数量
local last_tokens = tonumber(redis.call("get", tokens_key))
if last_tokens == nil then
last_tokens = capacity
end
-- 获取redis上一次时间戳
local last_refreshed = tonumber(redis.call("get", timestamp_key))
if last_refreshed == nil then
last_refreshed = 0
end
-- 时间差 = 当前时间戳 - 上一次时间戳
-- 比如只过去了一秒 delta=1
local delta = math.max(0, now-last_refreshed)
-- 当前最大量capacity=20,delta*rate=1*10=10
-- filled_tokens = 10个
local filled_tokens = math.min(capacity, last_tokens+(delta*rate))
-- 当前 10个>1个
local allowed = filled_tokens >= requested
-- new_tokens = 10个
local new_tokens = filled_tokens
-- 允许数量
local allowed_num = 0
-- 允许访问
if allowed then
-- new_tokens= 10 - 1 = 9
new_tokens = filled_tokens - requested
allowed_num = 1
end
redis.call("setex", tokens_key, ttl, new_tokens)
redis.call("setex", timestamp_key, ttl, now)
return { allowed_num, new_tokens }
启动一百个线程压测打印返回日志:
response: Response{allowed=true, headers={X-RateLimit-Remaining=19, X-RateLimit-Requested-Tokens=1, X-RateLimit-Burst-Capacity=20, X-RateLimit-Replenish-Rate=10}, tokensRemaining=-1}
response: Response{allowed=true, headers={X-RateLimit-Remaining=18, X-RateLimit-Requested-Tokens=1, X-RateLimit-Burst-Capacity=20, X-RateLimit-Replenish-Rate=10}, tokensRemaining=-1}
// 省略。。。。。。
response: Response{allowed=true, headers={X-RateLimit-Remaining=0, X-RateLimit-Requested-Tokens=1, X-RateLimit-Burst-Capacity=20, X-RateLimit-Replenish-Rate=10}, tokensRemaining=-1}
response: Response{allowed=false, headers={X-RateLimit-Remaining=0, X-RateLimit-Requested-Tokens=1, X-RateLimit-Burst-Capacity=20, X-RateLimit-Replenish-Rate=10}, tokensRemaining= -1}
此时客户端:HTTP response code: 429