Resilience4j 实践 - circuit breaker

微服务设计模式 - circuit breaker

circuit breaker 熔断器,在很多不同的领域都有这个定义,例如电路里面的熔断器,股票行业里面的熔断,当然我们这里的熔断指的是在计算机编程里面的,设计模式里的“熔断”。

简单的说,这就是一个状态机,然后状态机之间的转换逻辑;这个状态机有三个状态,分别是:

  • 开 open
  • 关 closed
  • 半开 half-open

这三个状态的转换关系,如下图:即正常状态下,熔断器是处于关闭的状态,内部维护一个错误数计数器,如果有错误发生,就增加这个计数器,直到一段时间内错误率达到或者超过阈值(failure threshold),此时熔断器打开,处于开的状态。熔断器开的状态下,所有新的操作都不进行了,这是为了给集群一点时间进行恢复,开启状态会有一个超时时间,超过这个时间之后,熔断器转为半开的状态;在半开状态下,熔断器会允许一定数量的操作,如果这些操作全部成功,那么熔断器会重置错误计数器,并回到关闭的状态;如果有一次失败了,那么熔断器又会变为开的状态,开启下一次超时计时。
状态转换

熔断器的思想也非常简单,只需要将你想要保护的方法包裹在一个熔断器里面,一旦熔断器开启了,后续请求都将返回error,或者其他降级处理,而根本不会再执行你保护的这个方法。一般熔断器开启也会配合告警一起使用。
熔断器效果

参考资料:
circuit breaker

Resilience4j - circuit breaker

这里介绍一些resilience4j中的circuit breaker的基础用法

maven 引入

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-all</artifactId>
    <version>1.7.0</version>
</dependency>

circuit breaker 初始化

CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
        .failureRateThreshold(20)
        .waitDurationInOpenState(Duration.ofMillis(500))
        .slidingWindowSize(5)
        .permittedNumberOfCallsInHalfOpenState(3)
        .recordException(new Predicate<Throwable>() {
            @Override
            public boolean test(Throwable throwable) {
                return true;
            }
        })
        .automaticTransitionFromOpenToHalfOpenEnabled(true)
        .build();

//注册
CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.of(circuitBreakerConfig);

CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("test");

几个熔断器的关键参数:

  • failureRateThreshold 即失败阈值的百分比,默认是50
  • waitDurationInOpenState 即熔断器开启时的超时时间设置
  • slidingWindowSize 即上文所述一段时间的这个一段时间的定义
  • permittedNumberOfCallsInHalfOpenState 即半开状态允许执行的操作数
  • recordException 定义一个Predicate,来判断是否发生的一个exception需要被错误计数器计数,这里直接返回true,就是任何exception发生,都会增加错误计数器
  • automaticTransitionFromOpenToHalfOpenEnabled 顾名思义,就是是否允许熔断器自动从开启状态超时后转到半开状态

保护我们的方法

假设我们需要保护的方法是:

public class RemoteService{
  public CompletableFuture<String> echoName(String name){
    System.out.println(name);
    return CompletableFuture.completedFuture(name);
  }
}

用circuit breaker 包裹一下:

String name = "rachel";
RemoteService remoteService = new RemoteService();
CheckedFunction0<CompletableFuture<String>> checkedSupplier = circuitBreaker.decorateCheckedSupplier(()->remoteService.echoName(name));
Try<CompletableFuture<String>> result = Try.of(checkedSupplier)
        .recover(CallNotPermittedException.class, throwable ->{
            System.out.println("熔断器已经打开,拒绝访问被保护方法~");
            return CompletableFuture.completedFuture("not permit");
        });

recover

javadoc文档上recover的介绍是: 此处提供了让我们可以从错误发生后进行降级处理等的地方~
recover介绍

一个小验证

package com.example.http.resilience4j;

import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.vavr.CheckedFunction0;
import io.vavr.control.Try;
import org.junit.jupiter.api.Test;

import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;

public class CircuitBreakerTest {
    private EchoService echoService;
    private CircuitBreakerRegistry circuitBreakerRegistry;
    public class EchoService {
        private AtomicInteger count = new AtomicInteger(0);
        public CompletableFuture<String> echoName(String name) {
            int num = count.getAndIncrement();
            if (num % 3 == 1) {
                throw new RuntimeException(name);
            }
            System.out.println("hello " + name + " ~ " + num);
            return CompletableFuture.completedFuture(name);
        }
    }
    @Test
    public void testCircuitBreaker() throws Throwable {
        //配置
        CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
                .failureRateThreshold(20)
                .waitDurationInOpenState(Duration.ofMillis(500))
                .slidingWindowSize(5)
                .permittedNumberOfCallsInHalfOpenState(3)
                .recordException(new Predicate<Throwable>() {
                    @Override
                    public boolean test(Throwable throwable) {
                        return true;
                    }
                })
                .automaticTransitionFromOpenToHalfOpenEnabled(true)
                .build();

        //注册
        circuitBreakerRegistry = CircuitBreakerRegistry.of(circuitBreakerConfig);
        echoService = new EchoService();

        for(int i= 0;i<10;i++){
            System.out.println("--------------------begin i={ " + i + " }");
            String result = echoWrapper("rachel").get();
            System.out.println("--------------------end i={ " + i + " } return: " + result);
        }

        Thread.sleep(500);
        for(int i= 5;i<10;i++){
            System.out.println("--------------------begin i={ " + i + " }");
            String result = echoWrapper("rachel").get();
            System.out.println("--------------------end i={ " + i + " } return: " + result);
        }
    }

    public CompletableFuture<String> echoWrapper(String name) {
        CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(name);
        CheckedFunction0<CompletableFuture<String>> checkedSupplier = circuitBreaker.decorateCheckedSupplier(()->echoService.echoName(name));
        Try<CompletableFuture<String>> result = Try.of(checkedSupplier)
                .recover(CallNotPermittedException.class, throwable ->{
                    System.out.println("熔断器已经打开,拒绝访问被保护方法~");
                    getCircuitBreakerStatus("熔断器打开中:", circuitBreaker);
                    return CompletableFuture.completedFuture("not permit");
                }).recover(throwable -> {
                    System.out.println(throwable.getLocalizedMessage() + ",方法被降级了~~");
                    getCircuitBreakerStatus("降级方法中:",circuitBreaker);
                    if("jojo".equals(name)){
                        return CompletableFuture.completedFuture("no available students");
                    }
                    return echoWrapper("jojo");
                });
        getCircuitBreakerStatus(circuitBreaker.getName() + "执行结束后:",circuitBreaker);
        return result.get();
    }

    public static void getCircuitBreakerStatus(String msg, CircuitBreaker circuitBreaker){
        CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics();
        // Returns the failure rate in percentage.
        float failureRate = metrics.getFailureRate();
        // Returns the current number of buffered calls.
        int bufferedCalls = metrics.getNumberOfBufferedCalls();
        // Returns the current number of failed calls.
        int failedCalls = metrics.getNumberOfFailedCalls();
        // Returns the current number of successed calls.
        int successCalls = metrics.getNumberOfSuccessfulCalls();
        // Returns the current number of not permitted calls.
        long notPermittedCalls = metrics.getNumberOfNotPermittedCalls();

        System.out.println(msg + "state=" +circuitBreaker.getState() + " , metrics[ failureRate=" + failureRate +
                ", bufferedCalls=" + bufferedCalls +
                ", failedCalls=" + failedCalls +
                ", successCalls=" + successCalls +
                ", notPermittedCalls=" + notPermittedCalls +
                " ]"
        );
    }
}

结果:

--------------------begin i={ 0 }
hello rachel ~ 0
rachel执行结束后:state=CLOSED , metrics[ failureRate=-1.0, bufferedCalls=1, failedCalls=0, successCalls=1, notPermittedCalls=0 ]
--------------------end i={ 0 } return: rachel
--------------------begin i={ 1 }
rachel,方法被降级了~~
降级方法中:state=CLOSED , metrics[ failureRate=-1.0, bufferedCalls=2, failedCalls=1, successCalls=1, notPermittedCalls=0 ]
hello jojo ~ 2
jojo执行结束后:state=CLOSED , metrics[ failureRate=-1.0, bufferedCalls=1, failedCalls=0, successCalls=1, notPermittedCalls=0 ]
rachel执行结束后:state=CLOSED , metrics[ failureRate=-1.0, bufferedCalls=2, failedCalls=1, successCalls=1, notPermittedCalls=0 ]
--------------------end i={ 1 } return: jojo
--------------------begin i={ 2 }
hello rachel ~ 3
rachel执行结束后:state=CLOSED , metrics[ failureRate=-1.0, bufferedCalls=3, failedCalls=1, successCalls=2, notPermittedCalls=0 ]
--------------------end i={ 2 } return: rachel
--------------------begin i={ 3 }
rachel,方法被降级了~~
降级方法中:state=CLOSED , metrics[ failureRate=-1.0, bufferedCalls=4, failedCalls=2, successCalls=2, notPermittedCalls=0 ]
hello jojo ~ 5
jojo执行结束后:state=CLOSED , metrics[ failureRate=-1.0, bufferedCalls=2, failedCalls=0, successCalls=2, notPermittedCalls=0 ]
rachel执行结束后:state=CLOSED , metrics[ failureRate=-1.0, bufferedCalls=4, failedCalls=2, successCalls=2, notPermittedCalls=0 ]
--------------------end i={ 3 } return: jojo
--------------------begin i={ 4 }
hello rachel ~ 6
rachel执行结束后:state=OPEN , metrics[ failureRate=40.0, bufferedCalls=5, failedCalls=2, successCalls=3, notPermittedCalls=0 ]
--------------------end i={ 4 } return: rachel
--------------------begin i={ 5 }
熔断器已经打开,拒绝访问被保护方法~
熔断器打开中:state=OPEN , metrics[ failureRate=40.0, bufferedCalls=5, failedCalls=2, successCalls=3, notPermittedCalls=1 ]
rachel执行结束后:state=OPEN , metrics[ failureRate=40.0, bufferedCalls=5, failedCalls=2, successCalls=3, notPermittedCalls=1 ]
--------------------end i={ 5 } return: not permit
--------------------begin i={ 6 }
熔断器已经打开,拒绝访问被保护方法~
熔断器打开中:state=OPEN , metrics[ failureRate=40.0, bufferedCalls=5, failedCalls=2, successCalls=3, notPermittedCalls=2 ]
rachel执行结束后:state=OPEN , metrics[ failureRate=40.0, bufferedCalls=5, failedCalls=2, successCalls=3, notPermittedCalls=2 ]
--------------------end i={ 6 } return: not permit
--------------------begin i={ 7 }
熔断器已经打开,拒绝访问被保护方法~
熔断器打开中:state=OPEN , metrics[ failureRate=40.0, bufferedCalls=5, failedCalls=2, successCalls=3, notPermittedCalls=3 ]
rachel执行结束后:state=OPEN , metrics[ failureRate=40.0, bufferedCalls=5, failedCalls=2, successCalls=3, notPermittedCalls=3 ]
--------------------end i={ 7 } return: not permit
--------------------begin i={ 8 }
熔断器已经打开,拒绝访问被保护方法~
熔断器打开中:state=OPEN , metrics[ failureRate=40.0, bufferedCalls=5, failedCalls=2, successCalls=3, notPermittedCalls=4 ]
rachel执行结束后:state=OPEN , metrics[ failureRate=40.0, bufferedCalls=5, failedCalls=2, successCalls=3, notPermittedCalls=4 ]
--------------------end i={ 8 } return: not permit
--------------------begin i={ 9 }
熔断器已经打开,拒绝访问被保护方法~
熔断器打开中:state=OPEN , metrics[ failureRate=40.0, bufferedCalls=5, failedCalls=2, successCalls=3, notPermittedCalls=5 ]
rachel执行结束后:state=OPEN , metrics[ failureRate=40.0, bufferedCalls=5, failedCalls=2, successCalls=3, notPermittedCalls=5 ]
--------------------end i={ 9 } return: not permit
--------------------begin i={ 5 }
rachel,方法被降级了~~
降级方法中:state=OPEN , metrics[ failureRate=60.0, bufferedCalls=5, failedCalls=3, successCalls=2, notPermittedCalls=5 ]
hello jojo ~ 8
jojo执行结束后:state=CLOSED , metrics[ failureRate=-1.0, bufferedCalls=3, failedCalls=0, successCalls=3, notPermittedCalls=0 ]
rachel执行结束后:state=HALF_OPEN , metrics[ failureRate=-1.0, bufferedCalls=0, failedCalls=0, successCalls=0, notPermittedCalls=0 ]
--------------------end i={ 5 } return: jojo
--------------------begin i={ 6 }
hello rachel ~ 9
rachel执行结束后:state=HALF_OPEN , metrics[ failureRate=-1.0, bufferedCalls=1, failedCalls=0, successCalls=1, notPermittedCalls=0 ]
--------------------end i={ 6 } return: rachel
--------------------begin i={ 7 }
rachel,方法被降级了~~
降级方法中:state=HALF_OPEN , metrics[ failureRate=-1.0, bufferedCalls=2, failedCalls=1, successCalls=1, notPermittedCalls=0 ]
hello jojo ~ 11
jojo执行结束后:state=CLOSED , metrics[ failureRate=-1.0, bufferedCalls=4, failedCalls=0, successCalls=4, notPermittedCalls=0 ]
rachel执行结束后:state=HALF_OPEN , metrics[ failureRate=-1.0, bufferedCalls=2, failedCalls=1, successCalls=1, notPermittedCalls=0 ]
--------------------end i={ 7 } return: jojo
--------------------begin i={ 8 }
hello rachel ~ 12
rachel执行结束后:state=OPEN , metrics[ failureRate=33.333332, bufferedCalls=3, failedCalls=1, successCalls=2, notPermittedCalls=0 ]
--------------------end i={ 8 } return: rachel
--------------------begin i={ 9 }
熔断器已经打开,拒绝访问被保护方法~
熔断器打开中:state=OPEN , metrics[ failureRate=33.333332, bufferedCalls=3, failedCalls=1, successCalls=2, notPermittedCalls=1 ]
rachel执行结束后:state=OPEN , metrics[ failureRate=33.333332, bufferedCalls=3, failedCalls=1, successCalls=2, notPermittedCalls=1 ]
--------------------end i={ 9 } return: not permit

Process finished with exit code 0
posted @ 2022-05-23 15:12  rachel_aoao  阅读(656)  评论(0编辑  收藏  举报