基于网关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 }--返回容许数和剩余令牌数

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