查看RequestRateLimiterGatewayFilterFactory类,其中有2个重要的属性defaultRateLimiter
,defaultRateLimiter
,它们都是接口类型:
@ConfigurationProperties("spring.cloud.gateway.filter.request-rate-limiter")
public class RequestRateLimiterGatewayFilterFactory extends
AbstractGatewayFilterFactory<RequestRateLimiterGatewayFilterFactory.Config> {
/**
* Key-Resolver key.
*/
public static final String KEY_RESOLVER_KEY = "keyResolver";
private static final String EMPTY_KEY = "____EMPTY_KEY__";
private final RateLimiter defaultRateLimiter;
private final KeyResolver defaultKeyResolver;
/**
* Switch to deny requests if the Key Resolver returns an empty key, defaults to true.
*/
private boolean denyEmptyKey = true;
/** HttpStatus to return when denyEmptyKey is true, defaults to FORBIDDEN. */
private String emptyKeyStatusCode = HttpStatus.FORBIDDEN.name();
public RequestRateLimiterGatewayFilterFactory(RateLimiter defaultRateLimiter,
KeyResolver defaultKeyResolver) {
super(Config.class);
this.defaultRateLimiter = defaultRateLimiter;
this.defaultKeyResolver = defaultKeyResolver;
}
...略
点进去 看它们的实现类:
-
keyResolver :它的意思是,限流的时候以什么维度来判断。 它有个实现类,
PrincipalNameKeyResolver
,它在没有配置的时候他就是默认类,以用户名来作为维度,它会从 ServerWebExchange检索 Principal并调用 Principal.getName()。因为Principal.getName()是空的,而denyEmptyKey =true,所以用它没有Principal的话,会返回forbidden403,就算设置denyEmptyKey =false,因为name是空,所以限流KeyResolver的键是空,限流会不生效。何况,我们一般会自己实现一个来取代。 -
rateLimiter:它的implement只有一个抽象类和redis的,redis是spring已经构建好了的,使用redis+lua脚本,采用的是令牌桶的限流方式,我们只需要按官网按需配置即可。
引入redis,顺带application文件配置redis。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
spring:
cloud:
gateway:
routes:
- id: requestratelimiter_route
uri: https://example.org
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10 #令牌每秒填充速度
redis-rate-limiter.burstCapacity: 20 #桶大小
redis-rate-limiter.requestedTokens: 1 #默认是1,每次请求消耗的令牌数
redis:
port: 6379
host: localhost #使用ip则把redis conf的 bind 127.0.0.1注释掉
timeout: 10S # 连接超时时间
lettuce:
pool:
#连接池最大连接数(使用负值表示没有限制)
max-active: 300
#连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1S
#连接池中的最大空闲连接
max-idle: 100
#连接池中的最小空闲连接
min-idle: 20
GatewayAutoConfiguration:
@Bean(name = PrincipalNameKeyResolver.BEAN_NAME)
@ConditionalOnBean(RateLimiter.class)
@ConditionalOnMissingBean(KeyResolver.class)
public PrincipalNameKeyResolver principalNameKeyResolver() {
return new PrincipalNameKeyResolver();
}
@Bean
@ConditionalOnBean({ RateLimiter.class, KeyResolver.class })
public RequestRateLimiterGatewayFilterFactory requestRateLimiterGatewayFilterFactory(
RateLimiter rateLimiter, KeyResolver resolver) {
return new RequestRateLimiterGatewayFilterFactory(rateLimiter, resolver);
}
RedisRateLimiter:
@ConfigurationProperties("spring.cloud.gateway.redis-rate-limiter")
public class RedisRateLimiter extends AbstractRateLimiter<RedisRateLimiter.Config>
implements ApplicationContextAware {
/**
* @deprecated use {@link Config#replenishRate}
*/
@Deprecated
public static final String REPLENISH_RATE_KEY = "replenishRate";
/**
* @deprecated use {@link Config#burstCapacity}
*/
@Deprecated
public static final String BURST_CAPACITY_KEY = "burstCapacity";
/**
* Redis Rate Limiter property name.
*/
public static final String CONFIGURATION_PROPERTY_NAME = "redis-rate-limiter";
/**
* Redis Script name.
*/
public static final String REDIS_SCRIPT_NAME = "redisRequestRateLimiterScript";
/**
* Remaining Rate Limit header name.
*/
public static final String REMAINING_HEADER = "X-RateLimit-Remaining";
/**
* Replenish Rate Limit header name.
*/
public static final String REPLENISH_RATE_HEADER = "X-RateLimit-Replenish-Rate";
/**
* Burst Capacity header name.
*/
public static final String BURST_CAPACITY_HEADER = "X-RateLimit-Burst-Capacity";
/**
* Requested Tokens header name.
*/
public static final String REQUESTED_TOKENS_HEADER = "X-RateLimit-Requested-Tokens";
...略
自行配置:
自定义KeyResolver,用uri作为限流维度:
@Component
public class UriKeyResolver implements KeyResolver {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
String requestPath = exchange.getRequest().getURI().getPath();
return Mono.just(requestPath);
}
}
配置
- name: RequestRateLimiter #不能简写,因为factory没有shortcut方法,key-resolver使用自己的bean
args:
key-resolver: '#{@uriKeyResolver}'
#deny-empty-key: false
redis-rate-limiter.replenishRate: 1 #令牌桶每秒填充速率
redis-rate-limiter.burstCapacity: 5 #令牌桶容量
redis-rate-limiter.requestedTokens: 1 #每次消耗令牌个数
测试结果:
生成2个redis key,它们的ttl都很短
- request_rate_limiter.{限流维度名字}.tokens
- request_rate_limiter.{限流维度名字}.timestamp
没有拿到token的,会返回429状态码,too many requests。
额外:
由于gateway转发的那方A,会调用另一番B,A对B有设置hystrix的配置,但是测试过后,对于gateway来说是完全无关的,B对A有降级熔断,该返回什么还是返回什么,gateway仅仅是个中介转发。
参考:
spring-cloud-gateway 11 限流 RequestRateLimiterGatewayFilterFactory 源码讲解
Spring Cloud 系列之 Gateway 服务网关(四) 限流算法讲解
网友评论