基于网关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());
}
};
}
条件选择如下
- 用户ip地址(桶是私有)
- 用户用户名(桶是私有)
- 微服的路径(桶是共享的)
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 }--返回容许数和剩余令牌数