background

These days analysis RequestRateLimterGatewayFilter source code, has been to RedisRateLimter current-limiting parameters is the problem of how to bind.

After booting up today, I saw the window opened by the weekend tracking code, and analyzed it again according to the process. Finally, I found the answer from the event monitoring/publishing principle of Spring container.

This article analyzes the details of the binding of limiting parameters for each route. The key is the trigger listener of the FilterArgsEvent event.

FilterArgsEvent Event is emitted

Step1, route definition loads interceptor.

This is done by RouteDefinitionRouteLocator loadGatewayFilters method of a class, it traverses the routing configuration list, step by step do the following:

  1. Gets an interceptor factory instance based on the interceptor factory nameGatewayFilterFactory factory = this.gatewayFilterFactories.get(definition.getName());
  2. Use this factory instance to assemble an interceptor configuration class objectObject configuration = this.configurationService.with(factory).xx.xxx.bind();
  3. Create an interceptor object using the configuration in Step 2 and add it to the target listGatewayFilter gatewayFilter = factory.apply(configuration).

Step2, create an assembly of interceptor configuration objects

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();
Copy the code

During the configuration instance, there is an operation to bind eventFunction, which is called in the Bind operation.

A FilterArgsEvent event is emitted in the Bind method of the AbstractBuilder class inside the ConfigurationService:

if (this.eventFunction ! = null && this.service.publisher ! = null) { ApplicationEvent applicationEvent = this.eventFunction.apply(bound, this.normalizedProperties); this.service.publisher.publishEvent(applicationEvent); }Copy the code

Step3. When and by whom was the event publishing class set up?

The puzzle is when and by whom the this.service.publisher property was assembled. This brings us back to the bottom of the Spring framework.

By the class declaration ConfigurationService implements ApplicationEventPublisherAware, The assembly class implements the Spring ApplicationEventPublisherAware interface, then its instance is managed to the Spring container, Spring will automatically inject the member variables.

That’s right, it’s automatically injected by Spring, it doesn’t need to be specified on the member variable or the constructor to specify the member variable, The Spring container will automatically call achieved ApplicationEventPublisherAware interface Bean setApplicationEventPublisher method set to release the event object.

Step4, the time at work in the factory

Next to the route factory configuration object created in the previous step, create the GatewayFilter object:

GatewayFilter gatewayFilter = factory.apply(configuration);
if (gatewayFilter instanceof Ordered) {
		ordered.add(gatewayFilter);
}
Copy the code

This completes the loadGatewayFilter process, which essentially generates one configuration object after another and calls the apply(Config) method of the factory to get one interceptor instance after another.

The FilterArgsEvent event listens for

Some simple interceptors that do not require additional configuration may not need to listen for this event. For the limiting interceptor, it listens for the FilterArgsEvent event and retrieves the attribute value of the Redis-rate-limiter prefix, which is assembled into the RedisRateLimiter.Config configuration.

The method of creating a limiting interceptor, apply(Config), analyzed earlier, only determines the configuration of the factory class, that is, the keyResolver and rateLimiter instances to be used, without the limiting parameter of rateLimiter for each route.

The key point is that the traffic limiting interceptor has two configurations, the interceptor factory configuration and the traffic limiting algorithm configuration, both of which need to be obtained from ARGS.

Spring event listening mechanism

Classes that implement the Spring event listener interface, when their instances are hosted in the Spring container, are added to the container’s listener list.

When the Spring container fires an event, it calls the onApplicationEvent method of the event listener class to notify them one by one.

The event listener mechanism is a low-level detail of Spring. All you need to do is inject a class object that implements the listener into the container, and it will automatically be added to the global list.

RedisRateLimiter class

Back to the RedisRateLimiter class, which is an ApplicationEventLinster implementation class because its subclass AbstractRateLimiter when FilterArgsEvent is triggered by the route load configuration, It listens for the event and performs the following process:

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); }}Copy the code

Therefore, during the loading of route configuration information, RedisRateLimter listens to FilterArgsEvent, obtains the parameters required by the traffic limiting algorithm, and organizes them into an instance of RedisRateLimiter. In this way, the traffic limiting parameters of each route are set.

RedisRateLimiter is a singleton. All routes that reference the interceptor need to save their configuration information, so it maintains a configuration set getConfig() with routeId as the key.

At this point, the assembly process of the limiting parameters of RedisRateLimiter is understood.

Specify different traffic limiters for the same route

A route can be configured with multiple interceptors. If we configure multiple traffic limiting interceptors, getConfig().put(routeId, routeConfig), the previous configuration will be overridden by the later configuration because it is stored with the routeId primary key.

Look at this configuration:

According to the onApplicationEvent processing flow of RedisRateLimiter, it stores the traffic limiting algorithm parameters of each route according to routId.

To configure multiple traffic limiting interceptors for the same route, only the last configuration takes effect by default, and the stored procedure of this configuration needs to be modified.

For details about this problem, see Spring Cloud Gateway Solution for Traffic Limiting and Multi-rule Adaptation.