SpringCloudGateway中ratelimiter源码分析

前言

在SpringCloudGateway中官方默认提供了基于Redis的分布式限流方案,对于大部分的场景开箱即用。但实际应用场景下,针对不同的业务场景可能需要进行定制化扩展,此时很有必要了解其工作原理,从而更加快速有效的实现自定义扩展。

正文

此部分将通过3个层面逐步展开:

  • Redis分布式限流的核心组件;
  • 如何配置路由;
  • 如何处理请求;
  • 如何刷新路由配置;

Redis分布式限流的核心组件

既然是Gateway模块的源码分析,根据springboot源码分析的套路,从GatewayAutoConfiguration类着手逐步展开,在GatewayAutoConfiguration类中能够找到如下bean实例的注册

@Bean(name = PrincipalNameKeyResolver.BEAN_NAME)
@ConditionalOnBean(RateLimiter.class)
public PrincipalNameKeyResolver principalNameKeyResolver() {
   
   return new PrincipalNameKeyResolver();
}

@Bean
@ConditionalOnBean({
   RateLimiter.class, KeyResolver.class})
public RequestRateLimiterGatewayFilterFactory requestRateLimiterGatewayFilterFactory(RateLimiter rateLimiter, PrincipalNameKeyResolver resolver) {
   
   return new RequestRateLimiterGatewayFilterFactory(rateLimiter, resolver);
}

其中

  • PrincipalNameKeyResolver 将作为默认的 KeyResolver 实现,其作用于redis存储的限流键key定义;
    - RequestRateLimiterGatewayFilterFactory 请求限流网关过滤器工厂类,其会默认注入已经定义的 RateLimiter 实例和 PrincipalNameKeyResolver 实例,此处说明 PrincipalNameKeyResolver 作为了默认的 KeyResolver 实现。

不难发现两个bean实例的注册均依赖于 RateLimiter 实例,该接口定义了判断是否能够放行的isAllowed方法,如下:

public interface RateLimiter<C> extends StatefulConfigurable<C> {
   
   Mono<Response> isAllowed(String routeId, String id);
   .....
}

在默认配置中,可以在 GatewayRedisAutoConfiguration类中找到如下其Bean实例的默认装配,目前SpringCloudGateway分布式限流官方提供的正是基于redis的实现,如下

@Bean
@ConditionalOnMissingBean
public RedisRateLimiter redisRateLimiter(ReactiveRedisTemplate<String, String> redisTemplate,
                               @Qualifier(RedisRateLimiter.REDIS_SCRIPT_NAME) RedisScript<List<Long>> redisScript,
                               Validator validator) {
   
   return new RedisRateLimiter(redisTemplate, redisScript, validator);
}

RedisRateLimiter 实例通过 @ConditionalOnMissingBean实现了条件注入,并不会被强制注入,其提供了自定义扩展的可能性。当前Bean实例依赖注入的 RedisScript实例,其指定了具体执行的lua脚本路径,

@Bean
@SuppressWarnings("unchecked")
public RedisScript redisRequestRateLimiterScript() {
   
   DefaultRedisScript redisScript = new DefaultRedisScript<>();
   redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("META-INF/scripts/request_rate_limiter.lua")));
   redisScript.setResultType(List.class);
   return redisScript;
}

该脚本已经在对应的jar包中可以直接查看,其默认采用的是令牌桶算法。需要注意的是该bean实例并不是条件注册的,而是默认强制注册。此时如果我们需要对脚本进行简单的调整,可以添加一个新的 RedisScript 实例,同时重新注册 RedisRateLimiter 实例,并重新指定其依赖注入的RedisScript实例为定义的新实例即可。

小节
到这里基本已经清楚SpringCloudGateway基于Redis实现的分布式限流的核心组件以及对应的实现:

  • RequestRateLimiterGatewayFilterFactory;
  • KeyResolver:PrincipalNameKeyResolver;
  • RateLimiter:RedisRateLimiter;
  • RedisScript :META-INF/scripts/request_rate_limiter.lua。

如何配置路由

Gateway中的限流目前是针对每个路由单独定义的,在了解如何针对每个路由定制化限流参数之前,需要先了解Gateway中是如何配置路由定位器的,从一个简单的application.yaml配置角度入手,其定义如下:

spring:
  cloud:
    gateway:
      routes:
        - id: consumer-service
          uri: http://127.0.0.1:8081
          predicates:
            - Path=/consumer-service/**
          filters:
            - name: RequestRateLimiter
              args:
                key-resolver: "#{@userKeyResolver}"
                redis-rate-limiter.replenishRate: 5
                redis-rate-limiter.burstCapacity: 10
            - RewritePath=/consumer-service/(?<segment>.*), /$\{
   segment}

其中明确指定将采用限流过滤器 RequestRateLimiter并配置了3个主要参数。
此时再次把焦点放在 GatewayAutoConfiguration类,根据spring.cloud.gateway前缀设定,上述 application.yaml中的配置项将绑定到 GatewayProperties实例中,

@Bean
public GatewayProperties gatewayProperties() {
   
   return new GatewayProperties();
}

根据 GatewayProperties中的路由配置信息,将生成基于properties的路由定义定位器 PropertiesRouteDefinitionLocator

@Bean
@ConditionalOnMissingBean
public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(GatewayProperties properties) {
   
   return new PropertiesRouteDefinitionLocator(properties);
}

默认情况下,系统还会注入一个基于内存的路由定义实例,如下 InMemoryRouteDefinitionRepository

@Bean
@ConditionalOnMissingBean(RouteDefinitionRepository.class)
public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() {
   
   return new InMemoryRouteDefinitionRepository();
}

在实际开发中可以定义多个路由定义定位器(此部分也是一个常规的扩展点,比如通过DB获取路由定义等),并通过 CompositeRouteDefinitionLocator将所有的路由定义定位器信息进行组合合并,

@Bean
@Primary
public RouteDefinitionLocator routeDefinitionLocator(List<RouteDefinitionLocator> routeDefinitionLocators) {
   
   return new CompositeRouteDefinitionLocator(Flux.fromIterable(routeDefinitionLocators));
}

在Debug模式下可以看到 routeDefinitionLocators包含了上述两个路由定义实例,如下

  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值