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 

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