基於網關GateWay實現限流-令牌桶 及原理解析

一、使用流程

1) 引入座標

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    <version>2.1.3.RELEASE</version>
</dependency>

2) 創建bean對象(根據什麼條件限流)

 @Bean
    public KeyResolver ipKeyResolver(){
        return new KeyResolver() {
            @Override
            public Mono<String> resolve(ServerWebExchange exchange) {
				//此處根據用戶的id做爲條件限流
                return Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
            }
        };
    }

條件選擇如下

  1. 用戶ip地址(桶是私有)
  2. 用戶用戶名(桶是私有)
  3. 微服的路徑(桶是共享的)

3)添加配置

      routes:
        - id: 微服務應用名
          uri: lb://微服務應用名
          predicates:
            - Path=/微服務應用名/**
          filters:
            - StripPrefix= 1
            - name: RequestRateLimiter #請求數限流 名字不能隨便寫
              args:
                key-resolver: "#{@ipKeyResolver}"   #限流依據的條件bean對象id
                redis-rate-limiter.replenishRate: 1 #令牌桶每秒填充平均速率
                redis-rate-limiter.burstCapacity: 1 #令牌桶總容量

二、GateWay網關實現默認令牌桶原理解析

a)springboot自動配置 E:/developSoftware/yntz_repository/org/springframework/cloud/spring-cloud-gateway-core/2.1.1.RELEASE/spring-cloud-gateway-core-2.1.1.RELEASE.jar!/META-INF/spring.factories 中org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration,

b)Lua腳本 位置: getway默認使用redis限流方式:核心內容就是如下的腳本中 spring-cloud-gateway-core-2.1.1.RELEASE.jar!/META-INF/scripts/request_rate_limiter.lua

執行原理流程解析如下:

1) 初始化對象-加載lua腳本

圖1 圖2 圖3

2)執行腳本

local tokens_key = KEYS[1]   -- 用戶令牌 ip
local timestamp_key = KEYS[2] --用戶今牌時間

local rate = tonumber(ARGV[1]) --放令牌速度(每秒)
local capacity = tonumber(ARGV[2])-- 桶大小
local now = tonumber(ARGV[3]) --當前時間秒值 
local requested = tonumber(ARGV[4]) --當前請求數默認 1

local fill_time = capacity/rate    --填滿桶時間
local ttl = math.floor(fill_time*2) -- 過期時間 =填滿桶時間的2倍

local last_tokens = tonumber(redis.call("get", tokens_key)) --獲取當前桶中剩餘令牌數
if last_tokens == nil then 
 last_tokens = capacity  --設置桶大小
end
--redis.log(redis.LOG_WARNING, "last_tokens " .. last_tokens)

local last_refreshed = tonumber(redis.call("get", timestamp_key)) --獲取上一次請求時間
if last_refreshed == nil then
 last_refreshed = 0 -- 沒有就是0
end

local delta = math.max(0, now-last_refreshed)  -- 當前時間和上次訪問時間的差值
local filled_tokens = math.min(capacity, last_tokens+(delta*rate)) --獲取當前桶中剩餘令牌數+  上次訪問和本次訪問期間放放桶中的令牌數
local allowed = filled_tokens >= requested --判斷桶中剩餘令牌數,是否大於1
local new_tokens = filled_tokens  --最新桶中令牌數
local allowed_num = 0  --允許的請求數
if allowed then
 new_tokens = filled_tokens - requested  --當前請求後的桶中的 令牌數
 allowed_num = 1 
end


redis.call("setex", tokens_key, ttl, new_tokens) -- new_tokens :當前剩餘令牌數
redis.call("setex", timestamp_key, ttl, now)  --當前時間

return { allowed_num, new_tokens }--返回容許數和剩餘令牌數

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