一. 示例demo
1. maven依赖
xml
复制代码
<!--resilience4j-->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-circuitbreaker</artifactId>
<!--<version>2.2.0</version>-->
<version>0.13.2</version>
</dependency>
2. ServerB
java
复制代码
public class ServerB {
private CircuitBreakerRegistry breakerRegistry;
private ServerC serverC = new ServerC(); //让服务B持有一个服务C的引用,用来表示正常服务间调用里的一个连接引用
ServerB() {
//初始化breaker注册器,可以利用该对象生产各种breaker对象(注:凡是用同一个注册器生产出来的breaker,都会继承注册器的配置属性)
breakerRegistry = CircuitBreakerRegistry.of(CircuitBreakerConfig.custom() //of方法里面放的就是breaker的配置属性对象
.enableAutomaticTransitionFromOpenToHalfOpen() //开启从全开状态经过下面的waitDurationInOpenState时间后自动切换到半开状态
.failureRateThreshold(50) //熔断器闭合状态下的错误率阈值,50表示50%,如果错误率达到这个阈值,那么熔断器将进入全熔断状态
.ringBufferSizeInClosedState(100) //熔断器闭合状态下,以该值为单位请求数,计算错误率,跟上面错误率阈值综合理解,这个值表示至少有100个请求,且错误50个以上才会触发全熔断
.ringBufferSizeInHalfOpenState(10) //熔断器半熔断状态下,以该值为单位请求数,计算错误率,跟上面错误率阈值综合理解,这个值表示至少有10个请求,且错误5个以上会再次触发全熔断,相比闭合状态,半熔断状态下更容易再次进入全熔断状态
.waitDurationInOpenState(Duration.ofMillis(1000L)) //熔断器全熔断状态持续的时间,全熔断后经过该时间后进入半熔断状态
.build());
}
//服务B通过服务C来获取到C的info信息,该方法就是用来干这个的,它会发起对服务C的调用
public String getCInfo(int id) {
//breaker对象是按照name划分全局单例的
CircuitBreaker breaker = breakerRegistry.circuitBreaker("getCInfo"); //这里给熔断器取个名,一般情况就是一个服务的path或方法名
try {
return breaker.executeCallable(() -> serverC.getCInfo(id));
} catch (CircuitBreakerOpenException e) { //一旦抛出该异常说明已经进入全熔断状态
//被熔断后的降级逻辑
return "服务C出错,触发服务B的降级逻辑";
} catch (Exception e) {
//熔断关闭或者半熔断状态下,C抛出的错误会被catch到这里
return "调用服务C出错";
}
}
public CircuitBreaker getBreaker() {
return breakerRegistry.circuitBreaker("getCInfo"); //为了方便做测试,这里返回对应的breaker对象
}
}
3. ServerC
java
复制代码
public class ServerC {
public String getCInfo(int id) {
if (id == 0) {
throw new RuntimeException("输入0异常");
}
return "id=" + id + "的C信息";
}
}
4. Test01
java
复制代码
public class Test01 {
private static ServerB serverB = new ServerB();
public static void main(String[] args) throws Exception {
testBreak();
}
/**
* 测试熔断/半开/关闭的全过程
* @throws Exception
*/
public static void testBreak() throws Exception {
//按照B服务里熔断器的配置,如果进行100次请求,有50次失败了,则对ServerC的调用进入全熔断状态
//1000ms后恢复为半熔断状态,半熔断状态下进行10次请求,如果有5次依然失败,则再次进入全熔断状态
for (int i = 0; i < 100; i++) {
if (i < 50) {
serverB.getCInfo(0); //前50次全部报错
} else {
serverB.getCInfo(1); //后50次全部成功
}
}
//断言:此时熔断器为全熔断状态
System.out.println(serverB.getBreaker().getState().equals(CircuitBreaker.State.OPEN));
//全熔断状态下并不会实际调用C,而是会走服务B的降级逻辑,即便我们输入的参数是对的,也一样会被降级
System.out.println(serverB.getCInfo(1));
System.out.println("-----------我是分隔符------------");
Thread.sleep(500L);
//断言:由于全熔断状态配置的持续时间时1000ms,所以500ms过去后,仍然是全熔断状态
System.out.println(serverB.getBreaker().getState().equals(CircuitBreaker.State.OPEN));
Thread.sleep(500L);
//断言:1000ms过后,熔断器处于半熔断状态
System.out.println(serverB.getBreaker().getState().equals(CircuitBreaker.State.HALF_OPEN));
//半熔断状态下会尝试恢复,所以会实际调用C,分别输入正确和错误的参数进行测试
System.out.println(serverB.getCInfo(1));
System.out.println(serverB.getCInfo(0));
System.out.println("-----------我是分隔符------------");
//半熔断状态下,只需要请求10次,有5次出错即可再次进入全熔断状态
for (int i = 0; i < 10; i++) {
if (i < 4) { //因为上面传过一次0了,所以这里只需要4次便可以重新回到全开状态
serverB.getCInfo(0); //前5次全部报错
} else {
serverB.getCInfo(1); //后5次全部成功
}
}
//断言:此时熔断器为全熔断状态
System.out.println(serverB.getBreaker().getState().equals(CircuitBreaker.State.OPEN));
//同样的,全熔断状态下并不会实际调用C,而是会走服务B的降级逻辑
System.out.println(serverB.getCInfo(1));
System.out.println("-----------我是分隔符------------");
//这时静待1000ms,再次进入半熔断状态,我们尝试恢复服务C的调用
Thread.sleep(1000L);
//这时我们让其10次请求里有6次成功
for (int i = 0; i < 10; i++) {
if (i < 6) { //前6次成功
serverB.getCInfo(1);
} else { //后4次失败
serverB.getCInfo(0);
}
}
//由于10次请求里只失败了4次,达不到50%的全开阈值,所以此时会恢复
//断言:此时熔断器为闭合状态
System.out.println(serverB.getBreaker().getState().equals(CircuitBreaker.State.CLOSED));
System.out.println(serverB.getCInfo(1)); //正常输出
System.out.println(serverB.getCInfo(0)); //走普通异常逻辑
System.out.println("-----------我是分隔符------------");
}
}
5. Test02
java
复制代码
public class Test02 {
private static ServerB serverB = new ServerB();
public static void main(String[] args) throws Exception {
// testRate1();
testRate2();
}
/**
* 测试熔断的次数时机1
*/
public static void testRate1() {
//首先闭合状态下单位请求仍然是100,现在让前49次全部失败
for (int i = 0; i < 100; i++) {
if (i < 49) {
serverB.getCInfo(0);
} else {
serverB.getCInfo(1);
}
}
//断言:虽然请求了100次,但是错误率并没有达到阈值(50%),所以这里仍然是闭合状态的
System.out.println(serverB.getBreaker().getState().equals(CircuitBreaker.State.CLOSED));
//这里再让其失败一次
serverB.getCInfo(0);
//断言:这里应该还是闭合状态的,按照100次单位请求来看,第一次失败的那个请求会被这次失败这个请求顶替掉(这里不理解没关系,下面有图)
System.out.println(serverB.getBreaker().getState().equals(CircuitBreaker.State.CLOSED));
}
/**
* 测试熔断的次数时机2
*/
public static void testRate2() throws InterruptedException {
//首先闭合状态下单位请求仍然是100,仍然让其错误49次,但现在让第2~50次失败
for (int i = 0; i < 100; i++) {
if (i != 0 && i < 50) { //第2~50次请求失败,总计失败49次
serverB.getCInfo(0);
} else {
serverB.getCInfo(1);
}
}
//断言:跟上面例子一样,错误率并没有达到阈值,仍然是闭合状态
System.out.println(serverB.getBreaker().getState().equals(CircuitBreaker.State.CLOSED));
// Thread.sleep(3000);
//这里再让其失败一次
serverB.getCInfo(0);
//断言:这里应该是全开状态,按照100次单位请求来看,第一次成功的那个请求会被这次失败这个请求顶替掉,然后凑够50次失败请求(参考图4)
System.out.println(serverB.getBreaker().getState().equals(CircuitBreaker.State.OPEN));
}
}