Resilience4j是一款轻量级,易于使用的容错库,其灵感来自于Netflix Hystrix,但是专为Java 8和函数式编程而设计。轻量级,因为库只使用了Vavr,它没有任何其他外部依赖下。相比之下,Netflix Hystrix对Archaius具有编译依赖性,Archaius具有更多的外部库依赖性,例如Guava和Apache Commons Configuration。
要使用Resilience4j,不需要引入所有依赖,只需要选择你需要的。Resilience4j提供了以下的核心模块和拓展模块:
CircuitBreaker通过具有三种正常状态的有限状态机实现: CLOSED,OPEN和HALF_OPEN以及两个特殊状态DISABLED和FORCED_OPEN。
Resilience4j记录请求状态的数据结构和Hystrix不同,Hystrix是使用滑动窗口来进行存储的,而Resilience4j采用的是Ring Bit Buffer(环形缓冲区)。
Ring Bit Buffer在内部使用BitSet这样的数据结构来进行存储,BitSet的结构如下图所示:
每一次请求的成功或失败状态只占用一个bit位,与boolean数组相比更节省内存。BitSet使用long[]数组来存储这些数据,意味着16个值(64bit)的数组可以存储1024个调用状态。
计算失败率需要填满环形缓冲区。如果环形缓冲区的大小为10,则必须至少请求满10次,才会进行故障率的计算,如果仅仅请求了9次,即使9个请求都失败,熔断器也不会打开。
但是CLOSE状态下的缓冲区大小设置为10并不意味着只会进入10个请求,在熔断器打开之前的所有请求都会被放入。
resilience4j-spring-boot集成了circuitbeaker、retry、bulkhead、ratelimiter几个模块,因为后续还要学习其他模块,就直接引入resilience4j-spring-boot依赖。
测试使用的IDE为idea,使用的springboot进行学习测试,首先引入maven依赖:
io.github.resilience4j
resilience4j-spring-boot
0.9.0
复制代码
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
复制代码
可以配置多个熔断器实例,使用不同配置或者覆盖配置。
以一个后端服务为例,利用熔断器保护该服务。
interface RemoteService {
List process() throws TimeoutException, InterruptedException;
}
复制代码
这是调用远端服务的连接器,我们通过调用连接器中的方法来调用后端服务。
public RemoteServiceConnector{
public List process() throws TimeoutException, InterruptedException {
List users;
users = remoteServic.process();
return users;
}
}
复制代码
各个配置项的作用,需要获取特定时候的熔断器状态:
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()))
;
}
复制代码
CircuitBreaker支持两种方式调用,一种是程序式调用,一种是AOP使用注解的方式调用。
在CircuitService中先注入注册器,然后用注册器通过熔断器名称获取熔断器。如果不需要使用降级函数,可以直接调用熔断器的executeSupplier方法或executeCheckedSupplier方法:
public class CircuitBreakerServiceImpl{
private CircuitBreakerRegistry circuitBreakerRegistry;
public List circuitBreakerNotAOP() throws Throwable {
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("service1");
CircuitBreakerUtil.getCircuitBreakerStatus("执行开始前:", circuitBreaker);
circuitBreaker.executeCheckedSupplier(remotServiceConnector::process);
}
}
复制代码
如果需要使用降级函数,则要使用decorate包装服务的方法,再使用Try.of().recover()进行降级处理,同时也可以根据不同的异常使用不同的降级方法:
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();
}
}
复制代码
首先在连接器方法上使用@CircuitBreaker(name=“”,fallbackMethod=“”)注解,其中name是要使用的熔断器的名称,fallbackMethod是要使用的降级方法,降级方法必须和原方法放在同一个类中,且降级方法的返回值需要和原方法相同,输入参数需要添加额外的exception参数,类似这样:
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;
}
}
复制代码
可使用多个降级方法,保持方法名相同,同时满足的条件的降级方法会触发最接近的一个(这里的接近是指类型的接近,先会触发离它最近的子类异常),例如如果process()方法抛出CallNotPermittedException,将会触发fallBack(CallNotPermittedException e)方法而不会触发fallBack(Throwable throwable)方法。
之后直接调用方法就可以了:
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;
}
}
复制代码
接下来进入测试,首先我们定义了两个异常,异常A同时在黑白名单中,异常B只在黑名单中:
recordExceptions: # 记录的异常 - com.example.resilience4j.exceptions.BusinessBException - com.example.resilience4j.exceptions.BusinessAException ignoreExceptions: # 忽略的异常 - com.example.resilience4j.exceptions.BusinessAException 然后对被保护的后端接口进行如下的实现:
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();
}
}
复制代码
使用CircuitBreakerServiceImpl中的AOP或者程序式调用方法进行单元测试,循环调用10次:
public class CircuitBreakerServiceImplTest{
private CircuitBreakerServiceImpl circuitService;
public void circuitBreakerTest() {
for (int i=0; i<10; i++){
circuitService.circuitBreakerNotAOP();
}
}
}
复制代码
同时也可以看出白名单所谓的忽略,是指不计入缓冲区中(即不算成功也不算失败),有降级方法会调用降级方法,没有降级方法会抛出异常,和其他异常无异。
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
复制代码