Spring Cloud Gateway custom filter practice (observe the change of circuit breaker state)

Keywords: cloud computing

Welcome to my GitHub

https://github.com/zq2599/blog_demos

Content: classification and summary of all original articles and supporting source code, involving Java, Docker, Kubernetes, DevOPS, etc;

Overview of this article

  • This article is the seventh in the Spring Cloud Gateway practice series. In the previous article, we learned about various built-in filters, and we are still working on them Circuit breaker function of Spring Cloud Gateway This paper deeply studies the filter of circuit breaker type (theory, practice and source code analysis). I believe you will have this question: no matter how many built-in filters can not cover all scenes, customization is the ultimate weapon
  • So today we will develop our own exclusive filter. As for the specific functions of this filter, in fact Above The foreshadowing has been buried, as shown in the following figure:

  • In short, it is to make a custom filter in a Spring Cloud Gateway application with a circuit breaker and print the state of the circuit breaker when processing each request, so that we can clearly know when the state of the circuit breaker changes and what it becomes Circuit breaker function of Spring Cloud Gateway Knowledge points
  • There are two types of filters: global and local. Here we choose local for a simple reason: our filter is to observe the circuit breaker, so it does not need to be effective globally, as long as it is effective in the route using the circuit breaker;

Know the routine in advance

  • Let's first look at the basic routine of custom local filters:
  1. Create a new class (I call it StatePrinterGatewayFilter.java) to implement the GatewayFilter and Ordered interfaces, focusing on the filter method, in which the main functions of the filter are implemented
  2. Create a new class (my name here is StatePrinterGatewayFilterFactory.java) to implement the AbstractGatewayFilterFactory method. The return value of the apply method is the instance of the StatePrinterGatewayFilter newly created in the previous step. The parameters of this method are the configuration under the filter node in the routing configuration, so that you can do some special processing according to the configuration, Then create an instance as the return value
  3. The StatePrinterGatewayFilterFactory class implements the < font color = "blue" > string name() < / font > method. The return value of this method is the < font color = "red" > name < / font > of the filter in the routing configuration file
  4. < font color = "blue" > string name() < / font > can also not be implemented, because there is a default implementation in the interface defining the method, as shown in the following figure, so the < font color = "red" > name < / font > of your filter in the routing configuration file can only be < font color = "blue" > stateprinter < / font >:

  1. As like as two peas, the filter is added to the configuration file.
  • The above is the basic routine of custom filter. It can be seen that it is still very simple. The next actual combat is also based on this routine
  • Before writing the custom filter code, there is a stumbling block waiting for us, that is, the basic function of our filter: how to obtain the state of the circuit breaker

How to obtain the status of circuit breaker

  • Above In the code analysis of, we know that the core functions of the circuit breaker are concentrated in the SpringCloudCircuitBreakerFilterFactory.apply method (yes, the apply method just mentioned). Open this class, as shown in the figure below. From the green box, it can be seen that the circuit breaker function comes from an object named < font color = "blue" > CB < / font >, This object is created by reactiveCircuitBreakerFactory in the red box:

  • Expand the reactiveCircuitBreakerFactory.create method on the right side of the red box in the figure above and continue to look. Finally, the ReactiveResilience4JCircuitBreakerFactory class is tracked. An extremely important variable is found, that is, the circuitBreakerRegistry in the red box in the figure below. Inside it, there is a concurrenthashmap (entrymap of inmemoryregistrystore), which stores all circuit breaker instances:

  • At this time, you should think that the key to getting the circuit breaker is to get the < font color = "blue" > circuitbreaker registry < / font > object in the red box above, but how? First, it is a private type. Second, although a method returns the object, this method is not public, as shown in the red box below:

  • Of course, this problem is not difficult for you. Yes, use reflection to modify the access rights of this method. We will do so in the code later
  • There is one last question left: circuitBreakerRegistry is a member variable of ReactiveResilience4JCircuitBreakerFactory. Where can I get this ReactiveResilience4JCircuitBreakerFactory?
  • If you have configured the circuit breaker, you will be familiar with the ReactiveResilience4JCircuitBreakerFactory. Setting the pair is the basic operation of configuring the circuit breaker. Review the previous code:
@Configuration
public class CustomizeCircuitBreakerConfig {

    @Bean
    public ReactiveResilience4JCircuitBreakerFactory defaultCustomizer() {

        CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom() //
                .slidingWindowType(CircuitBreakerConfig.SlidingWindowType.TIME_BASED) // The type of sliding window is time window
                .slidingWindowSize(10) // The size of the time window is 60 seconds
                .minimumNumberOfCalls(5) // At least 5 calls are required in the unit time window to start statistical calculation
                .failureRateThreshold(50) // When the call failure rate reaches 50% within the unit time window, the circuit breaker will be started
                .enableAutomaticTransitionFromOpenToHalfOpen() // The circuit breaker is allowed to automatically change from open state to half open state
                .permittedNumberOfCallsInHalfOpenState(5) // The number of normal calls allowed in the half open state
                .waitDurationInOpenState(Duration.ofSeconds(5)) // It takes 60 seconds for the circuit breaker to change from open state to half open state
                .recordExceptions(Throwable.class) // All exceptions are treated as failures
                .build();

        ReactiveResilience4JCircuitBreakerFactory factory = new ReactiveResilience4JCircuitBreakerFactory();
        factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
                .timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofMillis(200)).build())
                .circuitBreakerConfig(circuitBreakerConfig).build());

        return factory;
    }
}
  • Since ReactiveResilience4JCircuitBreakerFactory is a spring bean, we can use it freely with the Autowired annotation in the StatePrinterGatewayFilterFactory class
  • So far, the theoretical analysis has been completed, the problems have been solved, and the coding has started

Source download

  • The complete source code in this actual combat can be downloaded from GitHub. The address and link information are shown in the table below( https://github.com/zq2599/blo...):
namelinkremarks
Project Home https://github.com/zq2599/blo...The project is on the GitHub home page
git warehouse address (https)https://github.com/zq2599/blo...The warehouse address of the source code of the project, https protocol
git warehouse address (ssh)git@github.com:zq2599/blog_demos.gitThe project source code warehouse address, ssh protocol
  • There are multiple folders in this git project. The source code of this article is in the < font color = "blue" > spring cloud tutorials < / font > folder, as shown in the red box below:

  • There are several sub projects under the < font color = "blue" > spring cloud tutorials < / font > folder. The code of this chapter is < font color = "red" > circuit breaker gateway < / font >, as shown in the red box below:

code

  • The sub project < font color = "blue" > circuit breaker gateway < / font > has been created earlier. The circuit breaker has been added to this project. Now our filter code is the most appropriate in this project
  • Next, write the code according to the routine. First, StatePrinterGatewayFilter.java. If there are detailed comments in the code, it will not be wordy. Note that the return value of getOrder method is 10, which indicates the execution order of the filter:
package com.bolingcavalry.circuitbreakergateway.filter;

import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.vavr.collection.Seq;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.lang.reflect.Method;

public class StatePrinterGatewayFilter implements GatewayFilter, Ordered {

    private ReactiveResilience4JCircuitBreakerFactory reactiveResilience4JCircuitBreakerFactory;

    // Obtain the reactiveResilience4JCircuitBreakerFactory instance through the construction method
    public StatePrinterGatewayFilter(ReactiveResilience4JCircuitBreakerFactory reactiveResilience4JCircuitBreakerFactory) {
        this.reactiveResilience4JCircuitBreakerFactory = reactiveResilience4JCircuitBreakerFactory;
    }

    private CircuitBreaker circuitBreaker = null;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // Concurrency is not considered here. If it is a production environment, please add locking logic yourself
        if (null==circuitBreaker) {
            CircuitBreakerRegistry circuitBreakerRegistry = null;
            try {
                Method method = reactiveResilience4JCircuitBreakerFactory.getClass().getDeclaredMethod("getCircuitBreakerRegistry",(Class[]) null);
                // Set the getCircuitBreakerRegistry method to accessible with reflection
                method.setAccessible(true);
                // Execute getCircuitBreakerRegistry method with reflection to obtain circuitBreakerRegistry
                circuitBreakerRegistry = (CircuitBreakerRegistry)method.invoke(reactiveResilience4JCircuitBreakerFactory);
            } catch (Exception exception) {
                exception.printStackTrace();
            }

            // Get all circuit breaker examples
            Seq<CircuitBreaker> seq = circuitBreakerRegistry.getAllCircuitBreakers();
            // Filter by name. myCircuitBreaker comes from the routing configuration
            circuitBreaker = seq.filter(breaker -> breaker.getName().equals("myCircuitBreaker"))
                    .getOrNull();
        }

        // Take the circuit breaker status and judge it empty again, because the above operation may not get the circuit breaker
        String state = (null==circuitBreaker) ? "unknown" : circuitBreaker.getState().name();

        System.out.println("state : " + state);

        // Continue with the following logic
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 10;
    }
}
  • The next step is StatePrinterGatewayFilterFactory.java. There is no configuration here, so the input parameters of the apply method are useless. It should be noted that the reactiveResilience4JCircuitBreakerFactory is obtained through the Autowired annotation, and then passed to the StatePrinterGatewayFilter instance through the construction method:
package com.bolingcavalry.circuitbreakergateway.filter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;

@Component
public class StatePrinterGatewayFilterFactory extends AbstractGatewayFilterFactory<Object>
{
    @Autowired
    ReactiveResilience4JCircuitBreakerFactory reactiveResilience4JCircuitBreakerFactory;

    @Override
    public String name() {
        return "CircuitBreakerStatePrinter";
    }

    @Override
    public GatewayFilter apply(Object config)
    {
        return new StatePrinterGatewayFilter(reactiveResilience4JCircuitBreakerFactory);
    }
}
  • Finally, the configuration file. The complete configuration file is as follows. It can be seen that we have added the CircuitBreakerStatePrinter filter to the end:
server:
  #Service port
  port: 8081
spring:
  application:
    name: circuitbreaker-gateway
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
            - Path=/hello/**
          filters:
            - name: CircuitBreaker
              args:
                name: myCircuitBreaker
            - name: CircuitBreakerStatePrinter
  • Run the unit test class CircuitbreakerTest.java again. As shown in the red box below, the circuit breaker status has been printed. So far, we can accurately grasp the change of circuit breaker status:

Analyze the problem that the request is missed by the filter

  • There is an obvious problem that you, wise and wise, will not ignore: the corresponding circuit breaker status of the four consecutive responses in the green box in the above figure has not been printed. You know, our filter has to deal with each request. How can we miss four consecutive responses?
  • In fact, the reason is easy to infer: the filter of the circuit breaker circuit breaker is executed first, and then our circuit breaker stateprinter. The circuit breaker in the open state will directly return an error to the caller, and the subsequent filter will not be executed
  • Then the question arises: how to control the order of the two filter s, CircuitBreaker and CircuitBreakerStatePrinter, so that the CircuitBreakerStatePrinter executes first?
  • The CircuitBreaker stateprinter is our own code. We can adjust the order by modifying the return value of < font color = "blue" > stateprintergatewayfilter.getorder < / font >, but the CircuitBreaker is not our own code. What can we do?
  • The old rule is to look at the source code of the circuit breaker, which has been analyzed earlier. The most important code of the circuit breaker is the SpringCloudCircuitBreakerFilterFactory.apply method, as shown in the red box below. The generated filter is the implementation class of the GatewayFilter interface:

  • Look at the key code of loading the filter into the collection. In the RouteDefinitionRouteLocator.loadGatewayFilters method, as shown in the following figure, because the filter of CircuitBreaker does not implement the Ordered interface, it executes the code in the red box, and the value representing its order is equal to < font color = "red" > I + 1 < / font >, which is < font color = "red" >I < / font > is a zero based self increasing variable when traversing all filters in the routing configuration:

  • Reviewing our routing configuration, the CircuitBreaker comes first and the CircuitBreakerStatePrinter comes later. Therefore, when adding a CircuitBreaker, i is equal to 0, so the order of the CircuitBreaker is equal to i+1=1
  • The CircuitBreakerStatePrinter implements the Ordered interface, so the code in the red box will not be popular. Its order is equal to the value we write in the code, and we write 10
  • So: the order of the CircuitBreaker is equal to 1, and the CircuitBreaker stateprinter is equal to 10. Of course, the CircuitBreaker executes first!

Modify again

  • Knowing the reason, it's easy to change. My method is very simple: StatePrinterGatewayFilter no longer implements Ordered, so like the filter of circuit breaker, it executes the code in the red box in the figure above. In this way, whoever is in the front in the configuration file will execute first
  • The code will not be posted. You can delete the Ordered part of StatePrinterGatewayFilter by yourself
  • The configuration file is adjusted as follows:
server:
  #Service port
  port: 8081
spring:
  application:
    name: circuitbreaker-gateway
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: http://127.0.0.1:8082
          predicates:
            - Path=/hello/**
          filters:
            - name: CircuitBreakerStatePrinter
            - name: CircuitBreaker
              args:
                name: myCircuitBreaker
  • After modification, run CircuitbreakerTest.java again, as shown in the following figure. This time, each request will print the status of the circuit breaker at this time:

Summary of knowledge points

  • So far, even if the custom filter used to observe the state of the circuit breaker is completed, there are still many knowledge points in the whole process. Let's take an inventory:
  1. Conventional local filter development steps
  2. Logic of filter execution sequence
  3. Dependency injection and automatic assembly of spring
  4. filter source code of circuit breaker
  5. Reflection basic skills of java
  • This article and Circuit breaker function of Spring Cloud Gateway Combined with, we sincerely bring you the experience of combining theory with practice, and hope to bring you some references in the process of learning Spring Cloud Gateway;

You're not alone. Xinchen's original accompanies you all the way

  1. Java series
  2. Spring collection
  3. Docker series
  4. kubernetes series
  5. Database + middleware series
  6. DevOps series

Welcome to the official account: programmer Xin Chen

Wechat search "programmer Xinchen", I'm Xinchen, looking forward to traveling with you in the Java World
https://github.com/zq2599/blog_demos

Posted by sanju on Sun, 21 Nov 2021 18:08:31 -0800