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 article in the "Spring Cloud Gateway Actual Combat" series. In the previous article, we learned about various built-in filters. In the article "CircuitBreaker Function of Spring Cloud Gateway" , we studied the types of circuit breakers in depth. Filter (theoretical & actual combat & source code analysis), I believe you will have this question if you are smart: no amount of built-in can cover all the scenes, customization is the ultimate weapon
  • So today we will go to develop its own proprietary filter, the specific function of this filter is, in fact, previously has been paving the way, as shown below:

在这里插入图片描述

  • Simply put, it is to make a custom filter in a Spring Cloud Gateway application with a circuit breaker, and print the status of the circuit breaker when processing each request, so that we can clearly know the circuit breaker's status. When does the status change and what it becomes, it can be considered to complete the knowledge points "Circuit Breaker Function of Spring Cloud Gateway"
  • 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 there is no need to take effect globally, as long as it takes effect in the route using the circuit breaker;

Know the routine in advance

  • Let's first take a look at the basic routines of custom partial filters:
  1. Create a new class (I named it StatePrinterGatewayFilter.java here) to implement the GatewayFilter and Ordered interfaces. The focus is on the filter method. The main function of the filter is implemented here.
  2. Create a new class (named StatePrinterGatewayFilterFactory.java here) to implement the AbstractGatewayFilterFactory method. The return value of the apply method is the instance of the StatePrinterGatewayFilter 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 the return value
  3. The StatePrinterGatewayFilterFactory class implements the <font color="blue">String name()</font> method, and 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. This is because there is a default implementation in the interface that defines the method, as shown in the figure below, so that you can filter the <font in the routing configuration file color="red">name</font> can only be <font color="blue">StatePrinter</font>:

在这里插入图片描述

  1. In the configuration file, add your custom filter, the operation is exactly the same as adding the built-in filter before
  • The above is the basic routine of the 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 another obstacle waiting for us, which is the basic function of our filter: how to get the status of the circuit breaker

How to get the status of the circuit breaker

  • previous , we learned that the core function of the circuit breaker is concentrated in the SpringCloudCircuitBreakerFilterFactory.apply method (yes, it is the apply method just mentioned), open this class, as shown in the following figure, the circuit breaker function can be seen from the green box From an object named <font color="blue"> cb </font>, and this object was created by reactiveCircuitBreakerFactory in the red box:

在这里插入图片描述

  • Expand the reactiveCircuitBreakerFactory.create method on the right side of the red box in the above figure and continue to look. Finally, I traced the ReactiveResilience4JCircuitBreakerFactory class. An extremely important variable was found, which is the circuitBreakerRegistry in the red box in the figure below. There is a ConcurrentHashMap (entryMap of InMemoryRegistryStore) inside. All the circuit breaker instances are stored here:

在这里插入图片描述

  • You should have thought of it at this time. The key to getting the circuit breaker is to get the <font color ="blue">circuitBreakerRegistry</font> object in the red box above, but how to get it? First, it is a private type, and second, although there is a method that returns the object, this method is not public, as shown in the red box in the following figure:

在这里插入图片描述

  • Of course, this problem is not difficult for you, smart you, yes, use reflection to modify the access permissions of this method, let’s do that in the code later
  • The last question remains: circuitBreakerRegistry is a member variable of ReactiveResilience4JCircuitBreakerFactory. Where can I get this ReactiveResilience4JCircuitBreakerFactory?
  • If you have configured a circuit breaker, you are familiar with this ReactiveResilience4JCircuitBreakerFactory. Setting this object is like configuring the basic operation of a circuit breaker. Review the previous code:
@Configuration
public class CustomizeCircuitBreakerConfig {

    @Bean
    public ReactiveResilience4JCircuitBreakerFactory defaultCustomizer() {

        CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom() //
                .slidingWindowType(CircuitBreakerConfig.SlidingWindowType.TIME_BASED) // 滑动窗口的类型为时间窗口
                .slidingWindowSize(10) // 时间窗口的大小为60秒
                .minimumNumberOfCalls(5) // 在单位时间窗口内最少需要5次调用才能开始进行统计计算
                .failureRateThreshold(50) // 在单位时间窗口内调用失败率达到50%后会启动断路器
                .enableAutomaticTransitionFromOpenToHalfOpen() // 允许断路器自动由打开状态转换为半开状态
                .permittedNumberOfCallsInHalfOpenState(5) // 在半开状态下允许进行正常调用的次数
                .waitDurationInOpenState(Duration.ofSeconds(5)) // 断路器打开状态转换为半开状态需要等待60秒
                .recordExceptions(Throwable.class) // 所有异常都当作失败来处理
                .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 at will with the Autowired annotation in the StatePrinterGatewayFilterFactory class
  • At this point, the theoretical analysis has been completed, the problems have been solved, and the coding has started

Source download

nameLinkRemark
Project homepagehttps://github.com/zq2599/blog_demosThe project's homepage on GitHub
git warehouse address (https)https://github.com/zq2599/blog_demos.gitThe warehouse address of the source code of the project, https protocol
git warehouse address (ssh)git@github.com:zq2599/blog_demos.gitThe warehouse address of the source code of the project, ssh protocol
  • There are multiple folders in this git project. The source code of this article is under the <font color="blue">spring-cloud-tutorials</font> folder, as shown in the red box below:

在这里插入图片描述

  • There are multiple sub-projects under the <font color="blue">spring-cloud-tutorials</font> folder. The code for this article is <font color="red">circuitbreaker-gateway</font>, as shown in the red box below Shown:

在这里插入图片描述

coding

  • The subproject <font color="blue">circuitbreaker-gateway</font> was created in the previous article. Circuitbreaker-gateway has been added to this project. Now our filter code is written in this project.
  • Next, write the code in accordance with the routine. The first is StatePrinterGatewayFilter.java. If there are detailed comments in the code, it will no longer be verbose. It should be noted 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;

    // 通过构造方法取得reactiveResilience4JCircuitBreakerFactory实例
    public StatePrinterGatewayFilter(ReactiveResilience4JCircuitBreakerFactory reactiveResilience4JCircuitBreakerFactory) {
        this.reactiveResilience4JCircuitBreakerFactory = reactiveResilience4JCircuitBreakerFactory;
    }

    private CircuitBreaker circuitBreaker = null;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 这里没有考虑并发的情况,如果是生产环境,请您自行添加上锁的逻辑
        if (null==circuitBreaker) {
            CircuitBreakerRegistry circuitBreakerRegistry = null;
            try {
                Method method = reactiveResilience4JCircuitBreakerFactory.getClass().getDeclaredMethod("getCircuitBreakerRegistry",(Class[]) null);
                // 用反射将getCircuitBreakerRegistry方法设置为可访问
                method.setAccessible(true);
                // 用反射执行getCircuitBreakerRegistry方法,得到circuitBreakerRegistry
                circuitBreakerRegistry = (CircuitBreakerRegistry)method.invoke(reactiveResilience4JCircuitBreakerFactory);
            } catch (Exception exception) {
                exception.printStackTrace();
            }

            // 得到所有断路器实例
            Seq<CircuitBreaker> seq = circuitBreakerRegistry.getAllCircuitBreakers();
            // 用名字过滤,myCircuitBreaker来自路由配置中
            circuitBreaker = seq.filter(breaker -> breaker.getName().equals("myCircuitBreaker"))
                    .getOrNull();
        }

        // 取断路器状态,再判空一次,因为上面的操作未必能取到circuitBreaker
        String state = (null==circuitBreaker) ? "unknown" : circuitBreaker.getState().name();

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

        // 继续执行后面的逻辑
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 10;
    }
}
  • Next is StatePrinterGatewayFilterFactory.java. No configuration is needed 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);
    }
}
  • The last is the configuration file. The complete configuration file is as follows. It can be seen that we have added the CircuitBreakerStatePrinter filter and put it at the end:
server:
  #服务端口
  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 in the following figure, the circuit breaker status has been printed out, so far, we can accurately grasp the status change of the circuit breaker:

在这里插入图片描述

Analysis of the problem that the request is missed by the filter

  • There is an obvious problem that you will not overlook if you are smart and wise: the four consecutive responses in the green box above, the corresponding circuit breaker status are not printed out. You must know that our filter has to process every request. Yes, how could you miss four in a row?
  • In fact, the reason is easy to infer: the circuit breaker filter of the circuit breaker is executed first, and then our CircuitBreakerStatePrinter, and the circuit breaker in the open state will directly return an error to the caller, and the following filters will not be executed.
  • So the question is: how to control the order of the two filters CircuitBreaker and CircuitBreakerStatePrinter so that CircuitBreakerStatePrinter executes first?
  • CircuitBreakerStatePrinter is our own code. Modifying the return value of <font color="blue">StatePrinterGatewayFilter.getOrder</font> can adjust the order, but CircuitBreaker is not our own code. How can this be good?
  • The old rules, look at the source code of the circuit breaker, the previous article has already analyzed, the most important code of the circuit breaker is the SpringCloudCircuitBreakerFilterFactory.apply method, as shown in the red box in the following figure, the generated filter is the implementation class of the GatewayFilter interface:

在这里插入图片描述

  • Look at the key code to load the filter to the collection. In the RouteDefinitionRouteLocator.loadGatewayFilters method, as shown in the following figure, since the Filter of CircuitBreaker does not implement the Ordered interface, the code in the red box is executed, which represents the value of its order. Equal to <font color="red"> i+1 </font>, this <font color="red"> i </font> is a zero-based one when traversing all filters in the routing configuration Incremental variables only:

在这里插入图片描述

  • Looking back at our routing configuration, CircuitBreaker is in the front and CircuitBreakerStatePrinter is in the back, so when adding CircuitBreaker, i is equal to 0, then the order of CircuitBreaker is equal to i+1=1.
  • The CircuitBreakerStatePrinter implements the Ordered interface, so the code in the red box will not be popular, and its order is equal to the value we wrote in the code, we wrote 10
  • So: CircuitBreaker's order is equal to 1, CircuitBreakerStatePrinter is equal to 10. Of course, CircuitBreaker is executed first!

Modify again

  • Knowing the reason, it is easy to change it. My approach is very simple: StatePrinterGatewayFilter no longer implements Ordered, so it is the same as CircuitBreaker's filter, which executes the code in the red box above. In this way, in the configuration file, who puts it Whoever is in the front will execute first
  • The code will not be posted, you can delete the parts related to Ordered in StatePrinterGatewayFilter by yourself
  • The configuration file is adjusted as follows:
server:
  #服务端口
  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 the modification, run CircuitbreakerTest.java again, as shown in the figure below. This time, each request will print out the state of the circuit breaker at this time:

在这里插入图片描述

Summary of knowledge points

  • At this point, even if the custom filter for observing the state of the circuit breaker is completed, there are still a lot of knowledge points in the whole process, let's take a look:
  1. Conventional partial filter development steps
  2. The logic of filter execution order
  3. Dependency injection and automatic assembly of spring
  4. The filter source code of the circuit breaker
  5. Java's reflection basics

You are not lonely, Xinchen is with you all the way

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

Welcome to pay attention to the public account: programmer Xin Chen

Search "Programmer Xin Chen" on WeChat, I am Xin Chen, and I look forward to traveling the Java world with you...
https://github.com/zq2599/blog_demos

程序员欣宸
147 声望24 粉丝

热爱Java和Docker