[SpringCloud Technology Topics] "Resilience4j Getting Started Guide" (1) Getting Started Guide for Lightweight Fuse Framework

Basic introduction

Resilience4j is a lightweight, easy-to-use fault tolerance library inspired by Netflix Hystrix, but designed for Java 8 and functional programming. Lightweight, since the library only uses Vavr, it doesn't have any other external dependencies. In contrast, Netflix Hystrix has a compilation dependency on Archaius, which has more external library dependencies such as Guava and Apache Commons Configuration.

Using Resilience4j

To use Resilience4j, you don't need to import all dependencies, just choose the ones you need. Resilience4j provides the following core modules and extension modules:

core module

  • resilience4j-circuitbreaker: Circuit breaking
  • resilience4j-ratelimiter: Rate limiting
  • resilience4j-bulkhead: Bulkheading
  • resilience4j-retry: Automatic retrying (sync and async)
  • resilience4j-cache: Result caching
  • resilience4j-timelimiter: Timeout handling

Circuitbreaker

CircuitBreaker is implemented as a finite state machine with three normal states: CLOSED, OPEN and HALF_OPEN and two special states DISABLED and FORCED_OPEN .

  • When the circuit breaker is closed, all requests go through the circuit breaker.
  • If the failure rate exceeds a set threshold, the circuit breaker transitions from the closed state to the open state, at which point all requests are rejected.
  • After a period of time, the fuse will switch from the open state to the half-open state. At this time, only a certain number of requests will be put in, and the failure rate will be recalculated. If the failure rate exceeds the threshold, it will become open. If When the failure rate falls below the threshold, it becomes off.

Ring Bit Buffer (ring buffer)

The data structure of Resilience4j recording request status is different from that of Hystrix. Hystrix uses a sliding window for storage, while Resilience4j uses Ring Bit Buffer (ring buffer).

Ring Bit Buffer internally uses a data structure such as BitSet for storage. The structure of BitSet is shown in the following figure:

The success or failure status of each request only occupies one bit, which saves more memory than boolean arrays. BitSet uses long[] arrays to store these data, which means that an array of 16 values ​​(64bit) can store 1024 calling states.

Execution Monitoring Scope

Computing the failure rate requires filling the ring buffer. If the size of the ring buffer is 10, the failure rate must be calculated at least 10 times. If only 9 requests are made, even if all 9 requests fail, the fuse will not be opened.

request interception control

But setting the buffer size to 10 in the CLOSE state does not mean that only 10 requests will come in, all requests before the fuse is opened will be put in.

state transition mechanism

  • When the failure rate is higher than the set threshold, the state of the fuse will change from CLOSE to OPEN. At this time, all requests will throw a CallNotPermittedException exception.
  • After a period of time, the state of the fuse will change from OPEN to HALF_OPEN. There will also be a Ring Bit Buffer in the HALF_OPEN state to calculate the failure rate in the HALF_OPEN state. If it is higher than the configured threshold, it will be converted to OPEN. If it is lower than the threshold, it will be changed to CLOSE.
  • The buffer in the CLOSE state is different in that the buffer size in the HALF_OPEN state will limit the number of requests, and only the number of requests with the buffer size will be placed.
  • DISABLED (always allow access) and FORCED_OPEN (always deny access). These two states do not generate circuit breaker events (except for state transitions), and do not record the success or failure of the event. The only way to exit these two states is to trigger a state transition or reset the fuse.

Integration of SpringBoot

resilience4j-spring-boot integrates several modules of circuitbeaker, retry, bulkhead, and ratelimiter. Because other modules need to be learned later, the resilience4j-spring-boot dependency is directly introduced.

maven configuration pom.xml

The IDE used for the test is idea, and springboot is used for learning and testing. First, maven dependency is introduced:


    io.github.resilience4j
    resilience4j-spring-boot
    0.9.0

复制代码
application.yml configuration
resilience4j:
  circuitbreaker:
    configs:
      default:
        ringBufferSizeInClosedState: 5
        ringBufferSizeInHalfOpenState: 2
        waitDurationInOpenState: 10000
        failureRateThreshold: 60
        eventConsumerBufferSize: 10
        registerHealthIndicator: true
        automaticTransitionFromOpenToHalfOpenEnabled: false
        recordFailurePredicate:    com.example.resilience4j.exceptions.RecordFailurePredicate
        recordExceptions:
          - com.hyts.resilience4j.exceptions.Service1Exception
          - com.hyts.resilience4j.exceptions.Service2Exception
        ignoreExceptions:
          - com.example.resilience4j.exceptions.BusinessAException
    instances:
      service1:
        baseConfig: default
        waitDurationInOpenState: 5000
        failureRateThreshold: 20
      service2:
        baseConfig: default
复制代码

It is possible to configure multiple instances of Circuit Breaker, use different configurations, or override configurations.

Protected backend services

Take a backend service as an example, protect the service with a circuit breaker.

interface RemoteService {
    
    
    List process() throws TimeoutException, InterruptedException;
}
复制代码
The connector calls the service

This is the connector that calls the remote service. We call the backend service by calling the method in the connector.

public RemoteServiceConnector{
    
    
    public List process() throws TimeoutException, InterruptedException {
    
    
        List users;
        users = remoteServic.process();
        return users;
    }
}
复制代码
Monitor fuse status and events

The function of each configuration item needs to obtain the status of the fuse at a specific time:


public class CircuitBreakerUtil {
    
    

    public static void getCircuitBreakerStatus(String time, CircuitBreaker circuitBreaker){
    
    
        CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics();

        float failureRate = metrics.getFailureRate();

        int bufferedCalls = metrics.getNumberOfBufferedCalls();

        int failedCalls = metrics.getNumberOfFailedCalls();

        int successCalls = metrics.getNumberOfSuccessfulCalls();

        int maxBufferCalls = metrics.getMaxNumberOfBufferedCalls();

        long notPermittedCalls = metrics.getNumberOfNotPermittedCalls();
        log.info(time + "state=" +circuitBreaker.getState() + " , metrics[ failureRate=" + failureRate +
                ", bufferedCalls=" + bufferedCalls +
                ", failedCalls=" + failedCalls +
                ", successCalls=" + successCalls +
                ", maxBufferCalls=" + maxBufferCalls +
                ", notPermittedCalls=" + notPermittedCalls +
                " ]"
        );
    }

    public static void addCircuitBreakerListener(CircuitBreaker circuitBreaker){
    
    
        circuitBreaker.getEventPublisher()
                .onSuccess(event -> log.info("服务调用成功:" + event.toString()))
                .onError(event -> log.info("服务调用失败:" + event.toString()))
                .onIgnoredError(event -> log.info("服务调用失败,但异常被忽略:" + event.toString()))
                .onReset(event -> log.info("熔断器重置:" + event.toString()))
                .onStateTransition(event -> log.info("熔断器状态改变:" + event.toString()))
                .onCallNotPermitted(event -> log.info(" 熔断器已经打开:" + event.toString()))
        ;
    }
复制代码

call method

CircuitBreaker supports two ways of invocation, one is procedural invocation, and the other is invocation by AOP using annotations.

programmatic method call

First inject the register in CircuitService, and then use the register to obtain the fuse through the fuse name. If you do not need to use the downgrade function, you can directly call the executeSupplier method or executeCheckedSupplier method of the circuit breaker:

public class CircuitBreakerServiceImpl{
    
    

    private CircuitBreakerRegistry circuitBreakerRegistry;
    public List circuitBreakerNotAOP() throws Throwable {
    
    
        CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("service1");
        CircuitBreakerUtil.getCircuitBreakerStatus("执行开始前:", circuitBreaker);
        circuitBreaker.executeCheckedSupplier(remotServiceConnector::process);
    }
}
复制代码

If you need to use the downgrade function, you need to use the method of decorate to wrap the service, and then use Try.of().recover() for downgrade processing, and you can also use different downgrade methods according to different exceptions:

public class CircuitBreakerServiceImpl {
    
    

    private RemoteServiceConnector remoteServiceConnector;

    private CircuitBreakerRegistry circuitBreakerRegistry;
    public List circuitBreakerNotAOP(){
    
    

        CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("service1");
        CircuitBreakerUtil.getCircuitBreakerStatus("执行开始前:", circuitBreaker);

        CheckedFunction0> checkedSupplier = CircuitBreaker.

            decorateCheckedSupplier(circuitBreaker, remoteServiceConnector::process);

        Try> result = Try.of(checkedSupplier).

                    recover(CallNotPermittedException.class, throwable -> {
    
    
                        log.info("熔断器已经打开,拒绝访问被保护方法~");
                        CircuitBreakerUtil
                        .getCircuitBreakerStatus("熔断器打开中:", circuitBreaker);
                        List users = new ArrayList();
                        return users;
                    })
                    .recover(throwable -> {
    
    
                        log.info(throwable.getLocalizedMessage() + ",方法被降级了~~");
                        CircuitBreakerUtil
                        .getCircuitBreakerStatus("降级方法中:",circuitBreaker);
                        List users = new ArrayList();
                        return users;
                    });
            CircuitBreakerUtil.getCircuitBreakerStatus("执行结束后:", circuitBreaker);
            return result.get();
    }
}
复制代码
AOP style call method

First use the @CircuitBreaker(name="",fallbackMethod="") annotation on the connector method, where name is the name of the fuse to be used, fallbackMethod is the downgrade method to be used, and the downgrade method must be placed in the same place as the original method In a class, and the return value of the degraded method needs to be the same as the original method, the input parameter needs to add an additional exception parameter, similar to this:

public RemoteServiceConnector{
    
    

    public List process() throws TimeoutException, InterruptedException {
    
    
        List users;
        users = remoteServic.process();
        return users;
    }

    private List fallBack(Throwable throwable){
    
    
        log.info(throwable.getLocalizedMessage() + ",方法被降级了~~");
        CircuitBreakerUtil.getCircuitBreakerStatus("降级方法中:", circuitBreakerRegistry.circuitBreaker("backendA"));
        List users = new ArrayList();
        return users;
    }

    private List fallBack(CallNotPermittedException e){
    
    
        log.info("熔断器已经打开,拒绝访问被保护方法~");
        CircuitBreakerUtil.getCircuitBreakerStatus("熔断器打开中:", circuitBreakerRegistry.circuitBreaker("backendA"));
        List users = new ArrayList();
        return users;
    }

}
复制代码

Multiple downgrading methods can be used to keep the method name the same, and the downgrading method that meets the conditions at the same time will trigger the closest one (the proximity here refers to the proximity of the type, and the nearest subclass exception will be triggered first), for example, if process () method throws CallNotPermittedException, which will trigger the fallBack(CallNotPermittedException e) method instead of triggering the fallBack(Throwable throwable) method.

Then just call the method directly:

public class CircuitBreakerServiceImpl {
    
    

    private RemoteServiceConnector remoteServiceConnector;

    private CircuitBreakerRegistry circuitBreakerRegistry;

    public List circuitBreakerAOP() throws TimeoutException, InterruptedException {
    
    
        CircuitBreakerUtil
            .getCircuitBreakerStatus("执行开始前:",circuitBreakerRegistry.circuitBreaker("backendA"));
        List result = remoteServiceConnector.process();
        CircuitBreakerUtil
            .getCircuitBreakerStatus("执行结束后:", circuitBreakerRegistry.circuitBreaker("backendA"));
        return result;
    }
}
复制代码
use test

Next, enter the test. First, we define two exceptions. Anomaly A is in the black and white lists at the same time, and exception B is only in the blacklist:

recordExceptions: # Recorded exceptions - com.example.resilience4j.exceptions.BusinessBException - com.example.resilience4j.exceptions.BusinessAException ignoreExceptions: # Ignored exceptions - com.example.resilience4j.exceptions.BusinessAException Then the protected backend interface Perform the following implementation:

public class RemoteServiceImpl implements RemoteService {
    
    

    private static AtomicInteger count = new AtomicInteger(0);

    public List process() {
    
    
        int num = count.getAndIncrement();
        log.info("count的值 = " + num);
        if (num % 4 == 1){
    
    
            throw new BusinessAException("异常A,不需要被记录");
        }
        if (num % 4 == 2 || num % 4 == 3){
    
    
            throw new BusinessBException("异常B,需要被记录");
        }
        log.info("服务正常运行,获取用户列表");

        return repository.findAll();
    }
}
复制代码

Use the AOP or programmatic call method in CircuitBreakerServiceImpl for unit testing, and call it 10 times in a loop:

public class CircuitBreakerServiceImplTest{
    
    

    private CircuitBreakerServiceImpl circuitService;

    public void circuitBreakerTest() {
    
    
        for (int i=0; i<10; i++){
    
    

            circuitService.circuitBreakerNotAOP();
        }
    }
}
复制代码

At the same time, it can also be seen that the so-called ignoring of the whitelist means that it is not included in the buffer (that is, it is not counted as success or failure). If there is a downgrade method, the downgrade method will be called, and if there is no downgrade method, an exception will be thrown. different.

public class CircuitBreakerServiceImplTest{

@Autowired
private CircuitBreakerServiceImpl circuitService

@Test
public void circuitBreakerThreadTest() throws InterruptedException {
    ExecutorService pool = Executors.newCachedThreadPool()
    for (int i=0
        pool.submit(
            // circuitService::circuitBreakerAOP
            circuitService::circuitBreakerNotAOP)
    }
    pool.shutdown()

    while (!pool.isTerminated())

    Thread.sleep(10000)
    log.info("熔断器状态已转为半开")
    pool = Executors.newCachedThreadPool()
    for (int i=0
        pool.submit(
            // circuitService::circuitBreakerAOP
            circuitService::circuitBreakerNotAOP)
    }
    pool.shutdown()

    while (!pool.isTerminated())
    for (int i=0

    }
}
复制代码

}

resilience4j:
  circuitbreaker:
    configs:
      myDefault:
        automaticTransitionFromOpenToHalfOpenEnabled: true
复制代码

share resources

Information sharing
To obtain the above resources, please visit the open source project and click to jump

Guess you like

Origin blog.csdn.net/star20100906/article/details/132273611