Welcome to my GitHub
Here we classify and summarize all the original works of Xinchen (including supporting source code): https://github.com/zq2599/blog_demos
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:
- 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
- Create a new class (my name here is StatePrinterGatewayFilterFactory.java), which implements the AbstractGatewayFilterFactory method. The return value of the apply method is the instance of StatePrinterGatewayFilter newly created in the previous step. The input parameter of this method is the configuration under the filter node in the routing configuration, so you can do some special processing according to the configuration, and then create an instance as a return Return value
- The StatePrinterGatewayFilterFactory class implements the String name() method. The return value of this method is the name of the filter in the routing configuration file
- String name() 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 name of the filter in the routing configuration file can only be StatePrinter:
- 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, we learned that the core functions of the circuit breaker are concentrated in the spring cloud circuit breaker filterfactory In the apply method (yes, the apply method just mentioned), open this class, as shown in the following figure. It can be seen from the green box that the circuit breaker function comes from the object named cb, which is created by reactiveCircuitBreakerFactory in the red box:
- Expand reactivecircuitbreakerfactory on the right of the red box in the figure above The Create method continues to look. Finally, it traces the ReactiveResilience4JCircuitBreakerFactory class and finds an extremely important variable, that is, the circuitBreakerRegistry in the red box in the figure below. There is a concurrenthashmap (entrymap of inmemoryregistrystore) inside it, which stores all circuit breaker instances:
- At this point, you should think that the key to getting the circuit breaker is to get the circuitbreaker registry object in the red box in the figure 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/blog_demos):
name | link | remarks |
---|---|---|
Project Home | https://github.com/zq2599/blog_demos | The project is on the GitHub home page |
git warehouse address (https) | https://github.com/zq2599/blog_demos.git | The warehouse address of the source code of the project, https protocol |
git warehouse address (ssh) | git@github.com:zq2599/blog_demos.git | The project source code warehouse address, ssh protocol |
- There are multiple folders in this git project. The source code of this article is in the spring cloud tutorials folder, as shown in the red box below:
- There are several sub projects under the spring cloud tutorials folder. The code of this chapter is the circuit breaker gateway, as shown in the red box below:
code
- The sub project circuit breaker gateway was created earlier. Circuit breakers have been added to this project. Now our filter code is most appropriate in this project
- Next, write the code according to the routine. The first is stateprintergatewayfilter Java, if there are detailed comments in the code, it is no longer verbose. Note that the return value of the 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; } }
- Next 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. Exe again Java, as shown in the red box below, the status of the circuit breaker has been printed. So far, we can accurately grasp the change of the status of the circuit breaker:
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?
- CircuitBreakerStatePrinter is our own code. Modify stateprintergatewayfilter The return value of getorder can adjust the order, but the CircuitBreaker is not our own code. What can I do?
- The old rule is to look at the source code of the circuit breaker. It has been analyzed earlier. The most important code of the circuit breaker is springcloudcircuitbreakerfilterfactory The apply method, as shown in the red box below, generates a filter that is the implementation class of the GatewayFilter interface:
- Take another look at the key code that loads the filter into the collection, in routedefinitionroutelocator In the 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 i+1. This i 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 circuit breaker test again Java, 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:
- Conventional local filter development steps
- Logic of filter execution sequence
- Dependency injection and automatic assembly of spring
- filter source code of circuit breaker
- 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;