Spring Cloud Gateway traffic limiting for multi-rule solutions

You can also follow my public account: berry mousseweed, the article will also be updated, of course, the public account will have some resources to share with you ~

First of all, this article is using the Spring Cloud Gateway built-in or native Redis limiting!

background

The function of limiting traffic is not mentioned. It is usually to prevent some malicious requests. Unlimited request interfaces lead to long service processing time, which leads to response delay and service blocking, etc. Therefore, some interfaces with high frequency will add such functions as limiting traffic.


Traffic limiting is usually implemented for one route or one interface. The rule of traffic limiting is as follows: XXX route XXX can access XXX times at most within XXX time.

For example, the interface for querying user information [Route] supports a maximum of 10 accesses per user [Condition] per second [frequency time]. [Maximum frequency limit]

Take a clear point of example šŸŒ°, I within a second consecutive request 11 times [query user information interface], then the 11th time should be blocked, prompt request frequently, I believe that we will encounter similar situations in some double 11 such festivals ~

Let’s change the rule, another example šŸŒ° : [query user information interface] supports a maximum of 100 requests per second ~, that is, no matter who requests, anyway [query user information interface] supports a maximum of 100 requests per second, more than 100 will be blocked ~


The preceding two examples are used separately and specify one traffic limiting rule for one route. In actual service requirements, two or more rules may be used for one route simultaneously.

Such as:

[Interface for Querying user information] Each user supports a maximum of 10 accesses per second. This is rule 1, which limits the number of accesses for a single user.

In addition, the [Query user information interface] supports a maximum of 100 accesses per second ~, which is rule 2 to limit the number of accesses on this interface.

Let me give you a clearer example. Let’s say 10 people request [query user information interface] in the same second.

The first 8 people all make 10 requests in 1 second. (Each of the 8 people does not violate rule 1, and the total number of interface requests does not exceed 100. The interface can also make 20 requests without violating Rule 2.)

The ninth person requests 11 times. (If the traffic limiting condition of rule 1 is met, the 11th request of this person must be intercepted. The total number of requests by the interface does not exceed 100.

The 10th person requests 10 times, (without violating rule 1, the number of interface requests is 101, and the total number of interface requests is more than 100, which meets the traffic limiting condition of Rule 2, so the 10th request of this person must be blocked.)

Of course, the order of requests is ideal, but in real scenarios the order will vary


It may be a little too much to look at the text directly, but HERE I tease out a comparison chart šŸ‘‹ :

By default, one route (or interface) of the Spring Cloud Gateway can be configured with only one traffic limiting rule. This article is to solve this problem, let 1 route for multiple rules! šŸ¤Æ

Spring Cloud Gateway provides a set of traffic limiting scheme interface, and also based on Redis implementation of a set of traffic limiting scheme, this is the point to focus on the analysis of this article!

Familiar with the general process of Spring Cloud Gateway

General flow chart

The specific process will not be said here, I will directly say the main points involved in this article.

When the request enters the Gateway, the Gateway will assemble the corresponding filter according to the request route, and our flow limiting is also a filter. The Spring Cloud Gateway’s own implementation is: RequestRateLimiterGatewayFilterFactory, so we should analyze the source code, understanding of what it did roughly, we just good to know is there any way to adjust!

Common configuration use review

Before analysis, let’s review how traffic limiting is usually configured.

Add: RateLimiterConfig, first we define the limiting rule KeyResolver

Then configure traffic limiting for the route in application.yml, as shown in the following example:

spring:
  cloud:
    gateway:
      routes:
        - id: query_user_info_route
          uri: lb://user-center
          filters:
            - name: RequestRateLimiter
              args:
                # average token bucket fill rate per second
                redis-rate-limiter.replenishRate: 1
                # Token bucket upper limit
                redis-rate-limiter.burstCapacity: 10
                Get Bean objects from the Spring container using SpEL expressions
          # pathKeyResolver limits traffic by address
                key-resolver: "#{@remoteAddrKeyResolver}" # See RateLimiterConfig for details
Copy the code

As you can see, the filter (filters), our configuration is RequestRateLimiter, RequestRateLimiter here actually means the RequestRateLimiterGatewayFilterFactory, The GatewayFilterFactory ~ is omitted

The filter parameters are redis-rate-limiter and key-resolver.

For 1 traffic limiting rule we configure 1 filter and properties, so I want to add another rule, which we expect to do, example:

When I write, I still write

After writing it, it looks fine and the program will run, but you will find that only one of them actually works, and the one below overwrites the one above! Oh, bought a ~

There are two identical filters configured. In fact, they both run the filter twice, but at the same rate each time. It is equivalent to the same traffic limiting rule, which is verified twice ~

I will not show the specific running effect, next we come to the serious analysis of the source code, see what the situation!

RequestRateLimiterGatewayFilterFactory source code analysis

Only the core code analysis is listed here šŸ˜¬

The most important thing in this source code, combined with what we can see from the application.yml configuration, is:

RateLimiter: Flow limiting algorithm and its implementation (the actual implementation is the token bucket algorithm, I will not go into the depth here)

KeyResolver: key for limiting traffic for users and interfaces. When limiting traffic for IP addresses, this key is the requested IP address.

There is also the function limiter. IsAllowed, which is an important method to check whether the current limiting conditions are met.

The KeyResolver doesn’t seem to be an important factor in multi-rule limiting, so let’s get straight to RateLimiter

RateLimiter source code analysis

Open source a look, oh is interface, we have a look at the implementation of the class (idea in the picture below click the mark šŸ“Œ can be viewed)

Find two implementation classes, one is abstract class AbstractRateLimiter, one is based on the Redis implementation of RedisRateLimiter,(o悜 – 悜ā–½ć‚œ)oā˜†[BINGO! It must be RedisRateLimiter. Let’s just open it

RedisRateLimiter source code (don’t worry about the code, scroll down to šŸ˜¶)

Don’t worry about the code! In fact is based on Redis flow limit is how to achieve the algorithm, but why and flow limit can only have a rule, as if there is no relationshipšŸ¤£, it’s not here

šŸ“¢šŸ“¢ notice, but it extends AbstractRateLimiter, inheriting the AbstractRateLimiter class

AbstractRateLimiter source code analysis

AbstractRateLimiter source

Not much code, just a core method onApplicationEvent, the argument is a FilterArgsEvent, looks like the filter parameters args, and then do the processing

Tips, look at the name of the other people, a look to let people know about what the meaning, later we also pay attention to the next name!

Example of a core configuration for pasting a lower limit stream:

- name: RequestRateLimiter
         args:
           # average token bucket fill rate per second
           redis-rate-limiter.replenishRate: 1
           # Token bucket upper limit
           redis-rate-limiter.burstCapacity: 10
           Get the Bean object from the Spring container using SpEL expressions. PathKeyResolver limits the flow by address
           key-resolver: "#{@pathKeyResolver}"
Copy the code

Just to be clear, the filter parameter args is the limiting parameter, and

RedisRateLimiter extends AbstractRateLimiter<RedisRateLimiter.Config>
Copy the code

The onApplicationEvent initializes the routeConfig object, which is the Config object of RedisRateLimiter.Config.

There are only two attributes in Config, which is an important parameter of traffic limiting

Simply paste the RedisRateLimiter.Config code again

@Validated
public static class Config {
    @Min(1L)
    private int replenishRate;
    @Min(1L)
    private int burstCapacity = 1;

    public Config(a) {}/ / to omit...
}
Copy the code

Finally, there is this.getConfig().put(routeId, routeConfig);

Isn’t that what the routing and its corresponding current-limiting rules endures a Map in ~, the blind guess all know the enclosing getConfig () is a Map, you can go to AbstractStatefulConfigurable code, there is not shows the ~

AbstractRateLimiter<C> extends AbstractStatefulConfigurable<C>
Copy the code

This.getconfig ().put(routeId, routeConfig); Here I probably know what the problem is: we configure multiple traffic limiting rules for a 1 route and end up storing only one of them in a Map named Config!

To put it bluntly: In a Map, the Key is the routeId, and the Value is the routeConfig rule. Your routeId is fixed, even if you have multiple routeConfig rules stored in the Map, the latter routeConfig rule overwrites the previous one

šŸŽ‰šŸŽ‰šŸŽ‰šŸŽ‰

Retrofit scheme

Now that we’ve found the problem, let’s try to fix it! This.getConfig().put(routeId, routeConfig);

We should change the Key into Config instead of RouteId!

With what? With meUse the hashcode combination of routeId and KeyResolver

Get it straight before you remodel it, The Redis stream limiting implementation class is RedisRateLimiter, which inherits the abstract class AbstractRateLimiter. The method to be modified is AbstractRateLimiter.

So let’s rewrite RedisRateLimiter, rewrite the onApplicationEvent method!

Ok! Just Do It~

Custom DiyRedisRateLimiter

First, we create a new class called DiyRedisRateLimiter and copy the rest of the code from RedisRateLimiter!

Then override the onApplicationEvent method!

DiyRedisRateLimiter code:

Once created, we’ll initialize the class into Spring

/** * Author: Suremotoo */
@Configuration
public class RateLimiterConfig {

    /** * use a custom stream limiting class */
    @Bean
    @Primary
    public DiyRedisRateLimiter diyRedisRateLimiter(ReactiveRedisTemplate<String, String> redisTemplate,
        @Qualifier(DiyRedisRateLimiter.REDIS_SCRIPT_NAME) RedisScript<List<Long>> redisScript
    , Validator validator) {
        return new DiyRedisRateLimiter(redisTemplate, redisScript, validator);
    }
  
  / /... Other keyresolvers are omitted. See RateLimiterConfig in the previous description
}
Copy the code

That’s it, but mind you, we’re not done yet!

This is just putting one into the Map! But what about when you use it? Remember the isAllow method mentioned earlier? This method is apply in RequestRateLimiterGatewayFilterFactory method, so we have to rewrite here!

Custom DiyRequestRateLimiterGatewayFilterFactory

First of all, our new class 1, called DiyRequestRateLimiterGatewayFilterFactory, RequestRateLimiterGatewayFilterFactory inheritance

Then override the Apply method!

DiyRequestRateLimiterGatewayFilterFactory code sample:

Then in the application. When used in the yml DiyRequestRateLimiterGatewayFilterFactory with their definitions

Example:

Finally done ~

šŸŽ‰ Attached: beautiful PDF version

Due to typography and file management reasons, šŸ¤©šŸ¤©šŸ¤© follow my public id: Berry Rattrap send keyword: limiting flow multi-rule scheme

šŸŽ‰šŸŽ‰šŸŽ‰šŸŽ‰!