springgateway限流-令牌桶算法

限流配置

參見:

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());
    }

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 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章