限流配置
參見:
https://blog.csdn.net/forezp/article/details/85081162
http://www.ityouknow.com/springcloud/2019/01/26/spring-cloud-gateway-limit.html
https://www.cnblogs.com/sea520/p/11541789.html
限流算法圖示
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());
}
RedisRateLimiter中的isAllowed方法斷點截圖比對
對比之後的註解過程:
-- 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