background
After reading the source code of spring cloud gateway requestratelimiter, this paper analyzes how it provides default RedisRateLimiter support in an automated deployment mode.
This paper analyzes the source code based on version 3.1.0-SNAPSHOT.
Automatic injection interceptor factory
The code for current limiting interceptor instance injection in GatewayAutoConfiguration class is as follows:
@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); }
It requires two kinds of instances {ratelimit. Class, keyresolver. Class} in the container. This condition is true by default, because the GatewayRedisAutoConfiguration class injects the redisratelimit instance of the ratelimit implementation class by default:
@Bean @ConditionalOnMissingBean public RedisRateLimiter redisRateLimiter(ReactiveStringRedisTemplate redisTemplate, @Qualifier(RedisRateLimiter.REDIS_SCRIPT_NAME) RedisScript<List<Long>> redisScript, ConfigurationService configurationService) { return new RedisRateLimiter(redisTemplate, redisScript, configurationService); }
In this way, the current limiting interceptor is created.
The two attributes of the current limiting interceptor are mainly used to provide the default current limiting algorithm and the request for unique Key generation. If you want to change their implementation classes in the program, you can modify the configuration of the interceptor and specify the name of the Bean referenced by the two instances. The configuration class of the interceptor is defined as follows:
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; }
Attribute assembly
C routeConfig = newConfig(); if (this.configurationService != null) { this.configurationService.with(routeConfig).name(this.configurationPropertyName).normalizedProperties(args).bind(); }
In the configuration file:
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
How is the args parameter used?
There are several global FilterFactory classes specified by name. How are the different configurations for each Route handled?
How the parameter configuration of RedisRateLimiter instance is bound to a specific Route when calling isAllowed has not been clarified yet:
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(); . . . The following source code is omitted }
From this code, every time the current limiting method isAllowed is called, the read configuration in RouteConfig is related to the route.
The Book of Revelation
Because the configuration can specify that different routeids point to the same interceptor factory, and the interceptor factory instance is a single instance, the creation of the interceptor instance corresponding to each route must depend on the corresponding args configuration. In this way, the interceptor instance created by apply(Config) is unique to each route.