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的介绍是: 此处提供了让我们可以从错误发生后进行降级处理等的地方~
一个小验证
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