在使用spring-cloud-gateway时,可以配置请求限流;该限流基于redis实现, 用法如下
spring:
cloud:
gateway:
routes:
- id:requestratelimiter_route
uri:http://example.org
filters:
- name:RequestRateLimiter
args:
redis-rate-limiter.replenishRate:10 #允许用户每秒执行多少请求,而不会丢弃任何请求。这是令牌桶填充的速率。
redis-rate-limiter.burstCapacity:20 #一秒钟内允许执行的最大请求数。这是令牌桶可以容纳的令牌数。将此值设置为零将阻止所有请求。
key-resolver: "#{@userkeyResolver}" #根据关键字标识的限流
@Bean
KeyResolver userKeyResolver() {
return exchange -> Mono.just(RequestUtils.getIpAddress(exchange.getRequest()));
}
以上配置是基于请求ip做的限流配置,允许每个ip下一秒内可以有10个并请求,最多20个请求,但下一秒最多会变成10个请求;超过这个配置的请求将会被拒绝(返回409状态码)
源码分析:
源码位置在spring-cloud-gateway-core包下; 基于lua脚本实现
local tokens_key = KEYS[1] #请求唯一标识
local timestamp_key = KEYS[2] #请求时间
local rate = tonumber(ARGV[1]) #速率,如上面例子里的 10
local capacity = tonumber(ARGV[2]) #容量,如上面例子里的 20
local now = tonumber(ARGV[3]) #当前时间
local requested = tonumber(ARGV[4])#请求数量,默认是1
local fill_time = capacity/rate
local ttl = math.floor(fill_time*2) #得到过期时间
local last_tokens = tonumber(redis.call("get", tokens_key)) #剩余可用令牌,没有值则为桶的容量,上面例子里值范围是 0~20
if last_tokens == nil then
last_tokens = capacity
end
local last_refreshed = tonumber(redis.call("get", timestamp_key)) #上次请求时间,没值则为0
if last_refreshed == nil then
last_refreshed = 0
end
local delta = math.max(0, now-last_refreshed) #单前时间与上次请求时间的差值,最小是0;
local filled_tokens = math.min(capacity, last_tokens+(delta*rate)) #可用的令牌,最大为桶容量,范围是0~桶容量, 上面例子里是 0~20
local allowed = filled_tokens >= requested #是否允许请求, 可用令牌是否足够
local new_tokens = filled_tokens
local allowed_num = 0
if allowed then
new_tokens = filled_tokens - requested #可用令牌减去1
allowed_num = 1
end
redis.call("setex", tokens_key, ttl, new_tokens) #缓存可用令牌
redis.call("setex", timestamp_key, ttl, now) #缓存当前时间
return { allowed_num, new_tokens }
上面例子的入参情况是