RequestRateLimiter 的配置装配流程分析
Posted 毕小宝
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RequestRateLimiter 的配置装配流程分析相关的知识,希望对你有一定的参考价值。
背景
这几天分析 RequestRateLimterGatewayFilter
的源码,一直对 RedisRateLimter
的限流参数是怎么绑定的这个问题困扰。
今天开机后看到周末跟踪代码打开的窗口,又按流程分析了一遍,终于从 Spring 容器的事件监听/发布原理中找到了答案。
本文来分析每个路由的限流参数绑定的细节,关键在于 FilterArgsEvent
事件的触发监听上。
FilterArgsEvent 事件触发
step1,路由定义加载拦截器。
这是由RouteDefinitionRouteLocator
类的 loadGatewayFilters
方法完成的,它遍历路由配置列表,循序执行下列操作:
- 根据拦截器工厂名称,获取拦截器工厂实例
GatewayFilterFactory factory = this.gatewayFilterFactories.get(definition.getName())
; - 用该工厂实例组装一个拦截器配置类对象
Object configuration = this.configurationService.with(factory).xx.xxx.bind()
; - 用步骤 2 的配置创建一个拦截器对象,并加入到目标列表
GatewayFilter gatewayFilter = factory.apply(configuration)
。
step2,创建拦截器配置对象的装配过程
Object configuration = this.configurationService.with(factory)
.name(definition.getName())
.properties(definition.getArgs())
.eventFunction((bound, properties) -> new FilterArgsEvent(
// TODO: why explicit cast needed or java compile fails
RouteDefinitionRouteLocator.this, id, (Map<String, Object>) properties))
.bind();
配置实例 configuration
的过程中,有一个绑定 eventFunction
的操作,它在 bind
操作中被调用。
跟踪源码发现,在 ConfigurationService
的内部类 AbstractBuilder
的 bind
方法中触发了 FilterArgsEvent
事件:
if (this.eventFunction != null && this.service.publisher != null) {
ApplicationEvent applicationEvent = this.eventFunction.apply(bound, this.normalizedProperties);
this.service.publisher.publishEvent(applicationEvent);
}
step3,事件发布类是什么时候、由谁设置的呢?
疑点在于,这个 this.service.publisher
属性是什么时候、由谁装配的呢?这就回到了 Spring 框架的底层了。
由类声明ConfigurationService implements ApplicationEventPublisherAware
可知,这个装配类实现了 Spring 的 ApplicationEventPublisherAware
接口,那么它的实例被托管到 Spring 容器后,Spring 会自动注入该成员变量。
对的,就是 Spring 自动注入的,不需要成员变量上指定或者构造函数指定这个成员变量,Spring 容器会自动调用实现了 ApplicationEventPublisherAware
接口的 Bean 的 setApplicationEventPublisher
方法设置事件发布对象。
step4,工厂工作的时刻
接着用上一步创建的路由工厂配置对象,创建 GatewayFilter
对象:
GatewayFilter gatewayFilter = factory.apply(configuration);
if (gatewayFilter instanceof Ordered) {
ordered.add(gatewayFilter);
}
这就完成了 loadGatewayFilter
的流程,本质就是生成一个个配置对象,并调用工厂的 apply(Config)
方法,得到一个个拦截器实例。
FilterArgsEvent 事件监听
有些简单的拦截器不需要额外配置时,可能就不需要监听该事件。对于限流拦截器来说,它监听了 FilterArgsEvent
事件,并从中获取 redis-rate-limiter
前缀的属性值,最后组装成 RedisRateLimiter.Config
配置。
前面分析的创建限流拦截器的方法apply(Config)
,它的入参仅仅决定了工厂类的配置,即使用的 keyResolver
和 rateLimiter
实例,没有涉及每个路由的 RateLimiter
的限流参数 。
关键点在于:限流拦截器有两个配置,一个是拦截器工厂配置,一个是限流算法的配置,这两种配置信息都需要从 args
获取。
Spring 事件监听机制
对于实现了 Spring 事件监听接口 ApplicationListener
的类,它们的实例被托管到 Spring 容器后,会被加入容器的监听器列表。
当Spring 容器触发某事件的时候,会逐一调用事件监听器类的 onApplicationEvent
方法,通知它们。
事件监听机制是 Spring 底层的细节,开发者只需要向容器注入一个实现监听器的类对象,它会自动加入全局列表的。
RedisRateLimiter 类
接着回到 RedisRateLimiter
类,它是一个 ApplicationEventLinster
实现类,因为它的父类 AbstractRateLimiter
当路由装载配置触发 FilterArgsEvent
的时候,它会监听该事件,并执行如下流程:
public abstract class AbstractRateLimiter<C>
extends AbstractStatefulConfigurable<C>
implements RateLimiter<C>, ApplicationListener<FilterArgsEvent> {
@Override
public void onApplicationEvent(FilterArgsEvent event) {
Map<String, Object> args = event.getArgs();
if (args.isEmpty() || !hasRelevantKey(args)) {
return;
}
String routeId = event.getRouteId();
C routeConfig = newConfig();
if (this.configurationService != null) {
this.configurationService.with(routeConfig).name(this.configurationPropertyName).normalizedProperties(args)
.bind();
}
getConfig().put(routeId, routeConfig);
}
}
所以,在路由配置信息加载过程中,RedisRateLimter
监听到 FilterArgsEvent
,然后获取限流算法需要的参数,并且组织成 RedisRateLimiter.Config
实例,从而完成对每个路由限流参数设置的功能。
RedisRateLimiter
是单例,所有引用该拦截器的路由,都需要保存各自的配置信息,所以它维护了一个配置集合 getConfig()
,以 routeId
为 key
存储。
至此,就弄明白了 RedisRateLimiter
的限流参数的装配过程。
同一个路由指定不同限流器
一个路由可以配置多个拦截器,如果我们配置多个限流拦截器的话,getConfig().put(routeId, routeConfig)
,前面的配置会被后面的配置覆盖,因为它是以 routeId
为主键存储配置的。
看这个配置:
从 RedisRateLimiter
的 onApplicationEvent
处理流程来看,它按 routId
存储每个路由的限流算法参数。
要想给同一个路由配置多个限流拦截器,默认情况下只有最后一个配置生效,需要改造这个配置存储的过程。
这个问题可以参考《Spring Cloud Gateway 限流适配多规则的解决方案》。
以上是关于RequestRateLimiter 的配置装配流程分析的主要内容,如果未能解决你的问题,请参考以下文章
SpringCloud gateway RequestRateLimiter 源码串联分析
SpringCloud gateway RequestRateLimiter 源码串联分析
又被面试官装到了:Spring Cloud Gateway 网关限流怎么做?