背景
看了一遍 SpringCloud gateway RequestRateLimiter
源码,本文来串联分析下它是如何用自动化部署方式提供默认的 RedisRateLimiter
支持的。
本文基于 3.1.0-SNAPSHOT 版本的源码进行分析。
自动注入拦截器工厂
GatewayAutoConfiguration
类中关于限流拦截器实例注入的代码如下:
@Bean(name = PrincipalNameKeyResolver.BEAN_NAME)
@ConditionalOnBean(RateLimiter.class)
@ConditionalOnMissingBean(KeyResolver.class)
@ConditionalOnEnabledFilter(RequestRateLimiterGatewayFilterFactory.class)
public PrincipalNameKeyResolver principalNameKeyResolver() {
return new PrincipalNameKeyResolver();
}
@Bean
@ConditionalOnBean({ RateLimiter.class, KeyResolver.class })
@ConditionalOnEnabledFilter
public RequestRateLimiterGatewayFilterFactory requestRateLimiterGatewayFilterFactory(RateLimiter rateLimiter,
KeyResolver resolver) {
return new RequestRateLimiterGatewayFilterFactory(rateLimiter, resolver);
}
它需要容器中存在两种类的实例 { RateLimiter.class, KeyResolver.class }
,这个条件默认是成立的,因为 GatewayRedisAutoConfiguration
这个类默认为它注入了 RateLimiter
的实现类RedisRateLimiter
实例:
@Bean
@ConditionalOnMissingBean
public RedisRateLimiter redisRateLimiter(ReactiveStringRedisTemplate redisTemplate,
@Qualifier(RedisRateLimiter.REDIS_SCRIPT_NAME) RedisScript<List<Long>> redisScript,
ConfigurationService configurationService) {
return new RedisRateLimiter(redisTemplate, redisScript, configurationService);
}
这样的话,就创建了限流拦截器了。
限流拦截器这两个属性,它们主要用于提供默认的限流算法和请求唯一性 Key 生成这两项信息的。如果程序中想要改变它们的实现类,可以修改拦截器的配置,指定这两个实例引用的 Bean 的名称,这个拦截器的配置类是这样定义的:
public static class Config implements HasRouteId {
private KeyResolver keyResolver;
private RateLimiter rateLimiter;
private HttpStatus statusCode = HttpStatus.TOO_MANY_REQUESTS;
private Boolean denyEmptyKey;
private String emptyKeyStatus;
private String routeId;
}
属性装配
C routeConfig = newConfig();
if (this.configurationService != null) {
this.configurationService.with(routeConfig).name(this.configurationPropertyName).normalizedProperties(args).bind();
}
配置文件中的:
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
args
参数是怎么被使用的呢?name
指定的 FilterFactory
类全局有几个,针对每个 Route
的不同配置又是怎么处理的呢?
针对 RedisRateLimiter
的实例的参数配置是怎么在调用 isAllowed
时绑定到特定 Route 上的,这个过程还没有整理明白:
public Mono<Response> isAllowed(String routeId, String id) {
if (!this.initialized.get()) {
throw new IllegalStateException("RedisRateLimiter is not initialized");
}
Config routeConfig = loadConfiguration(routeId);
// How many requests per second do you want a user to be allowed to do?
int replenishRate = routeConfig.getReplenishRate();
// How much bursting do you want to allow?
int burstCapacity = routeConfig.getBurstCapacity();
// How many tokens are requested per request?
int requestedTokens = routeConfig.getRequestedTokens();
。。。后面源码略
}
从这段代码来看,每次调用限流方法 isAllowed
时,读取到的 RouteConfig
中配置都是与该路由有关的。
启示录
因配置可以指定不同 routeId
都指向同一个拦截器工厂,而拦截器工厂实例是单例的,那么各个路由对应的拦截器实例的创建必定是依赖于对应的 args
配置,这样的话 apply(Config)
创建的拦截器实例就是各路由特有的了。