簡(jiǎn)介
微服務(wù)結(jié)構(gòu)中玄窝,服務(wù)調(diào)用其他服務(wù)垒迂,如果服務(wù)提供者不可用械姻,會(huì)引起服務(wù)調(diào)用者不可用,進(jìn)而引起整個(gè)調(diào)用鏈上所有的服務(wù)可用机断,熔斷器提供了服務(wù)提供者不可用時(shí)楷拳,服務(wù)調(diào)用者能夠快速返回的機(jī)制
這里簡(jiǎn)單介紹resilience4j-cricuit breaker的使用,包括在sping boot中使用yml文件配置熔斷器和注解@CircuitBreaker
使用熔斷器
基本功能
resilence4j-circuit breaker 熔斷器包括三個(gè)狀態(tài):關(guān)閉吏奸、開啟欢揖、半開。
- 服務(wù)正常情況下熔斷器處于關(guān)閉狀態(tài)奋蔚,一切正常浸颓,
- 在熔斷器判斷服務(wù)不可達(dá)時(shí)進(jìn)入開啟狀態(tài),服務(wù)將拒絕請(qǐng)求旺拉,轉(zhuǎn)而執(zhí)行fallback方法返回产上,
- 在開啟狀態(tài)固定時(shí)間之后,熔斷器進(jìn)入半開狀態(tài)蛾狗,服務(wù)將接受固定個(gè)數(shù)的請(qǐng)求晋涣,熔斷器根據(jù)這些請(qǐng)求執(zhí)行的失敗概率來(lái)決定是進(jìn)入開啟狀態(tài)還是關(guān)閉狀態(tài)
熔斷器統(tǒng)計(jì)失敗率
resilience4j 默認(rèn)是使用基于請(qǐng)求數(shù)量的滑動(dòng)窗口統(tǒng)計(jì)失敗率,具體數(shù)量可配置沉桌,默認(rèn)配置為100谢鹊,resilience4j只統(tǒng)計(jì)最新的100個(gè)請(qǐng)求的成功失敗情況,如果有新的請(qǐng)求最老的請(qǐng)求會(huì)被踢出
配置詳解
配置屬性 | 默認(rèn)值 | 描述 |
---|---|---|
failureRateThreshold | 50 | 百分比,當(dāng)失敗率等于或大于閾值時(shí)留凭,熔斷器狀態(tài)從關(guān)閉變?yōu)殚_啟 |
slowCallRateThreshold | 100 | 百分比佃扼,熔斷器把調(diào)用時(shí)間大于slowCallDurationThreshold的調(diào)用視為慢調(diào)用,當(dāng)慢調(diào)用比例大于等于閾值時(shí)蔼夜,熔斷器開啟 |
slowCallDurationThreshold | 60000 [ms] | 長(zhǎng)于該時(shí)間的調(diào)用被視為慢調(diào)用 |
permittedNumberOfCallsInHalfOpenState | 10 | 熔斷器在半開狀態(tài)下允許通過的調(diào)用次數(shù)兼耀,通過計(jì)算這些調(diào)用的失敗比例來(lái)判斷切換為關(guān)閉還是開啟狀態(tài) |
maxWaitDurationInHalfOpenState | 0 | 熔斷器在半開狀態(tài)下的最長(zhǎng)等待時(shí)間,超過該配置值的話求冷,熔斷器會(huì)從半開狀態(tài)恢復(fù)為開啟狀態(tài)瘤运。配置是0時(shí)表示熔斷器會(huì)一直處于半開狀態(tài),直到所有允許通過的訪問結(jié)束匠题。 |
slidingWindowType | COUNT_BASED | 滑動(dòng)窗口的類型拯坟,當(dāng)熔斷器關(guān)閉時(shí),將調(diào)用的結(jié)果記錄在滑動(dòng)窗口中韭山∮艏荆滑動(dòng)窗口的類型可以是count-based或time-based冷溃。如果滑動(dòng)窗口類型是COUNT_BASED,將會(huì)統(tǒng)計(jì)記錄最近slidingWindowSize次調(diào)用的結(jié)果梦裂。如果是TIME_BASED秃诵,將會(huì)統(tǒng)計(jì)記錄最近slidingWindowSize秒的調(diào)用結(jié)果。 |
slidingWindowSize | 100 | 滑動(dòng)窗口的大小塞琼。 |
minimumNumberOfCalls | 100 | 熔斷器計(jì)算失敗率或慢調(diào)用率之前所需的最小調(diào)用數(shù)(每個(gè)滑動(dòng)窗口周期)。例如禁舷,如果minimumNumberOfCalls為10彪杉,則必須至少記錄10個(gè)調(diào)用,然后才能計(jì)算失敗率。如果只記錄了9次調(diào)用,即使所有9次調(diào)用都失敗铐达,熔斷器也不會(huì)開啟凳宙。 |
waitDurationInOpenState | 60000 [ms] | 熔斷器從開啟過渡到半開應(yīng)等待的時(shí)間。 |
automaticTransitionFromOpenToHalfOpenEnabled | false | 設(shè)置為true妆艘,則意味著熔斷器在waitDurationInOpenState時(shí)間后將自動(dòng)從開啟狀態(tài)過渡到半開狀態(tài)。設(shè)置為false,則只有在waitDurationInOpenState時(shí)間后發(fā)出調(diào)用時(shí)才會(huì)轉(zhuǎn)換到半開 |
recordExceptions | empty | 異常列表谱轨,除非通過ignoreExceptions顯式忽略,否則與列表中某個(gè)匹配或繼承的異常都將被視為失敗吠谢。如果指定異常列表土童,則所有其他異常均視為成功,除非它們被ignoreExceptions顯式忽略工坊。 |
ignoreExceptions | empty | 異常列表献汗,任何與列表之一匹配或繼承的異常既不會(huì)被視為失敗也不會(huì)被視為成功,即使異常是recordExceptions的一部分王污。 |
recordException | throwable -> true | 一個(gè)自定義的Predicate罢吃,用于評(píng)估異常是否應(yīng)記錄為失敗。如果異常應(yīng)計(jì)為失敗昭齐,則斷言必須返回true尿招。如果出斷言返回false,應(yīng)算作成功阱驾,除非ignoreExceptions顯式忽略異常泊业。 |
ignoreException | throwable -> false | 自定義Predicate來(lái)判斷一個(gè)異常是否應(yīng)該被忽略,如果應(yīng)忽略異常啊易,則謂詞必須返回true吁伺。如果異常應(yīng)算作失敗,則斷言必須返回false租谈。 |
演示使用
- 創(chuàng)建一個(gè)模擬的外部服務(wù)篮奄,前10次調(diào)用成功捆愁,后面的調(diào)用返回異常
- 自定義CircuitBreakerConfig,修改滑動(dòng)窗口大小為10窟却,默認(rèn)情況下50%的調(diào)用失敗會(huì)進(jìn)入開啟狀態(tài)昼丑,修改熔斷器從開啟到半開啟時(shí)間為10s
- 每隔1秒使用熔斷器調(diào)用一次模擬服務(wù)
需要準(zhǔn)備的依賴包,resilience4j的spring boot包夸赫,里面包括了resilience4j所有功能的包和自動(dòng)配置功能
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>1.7.0</version>
</dependency>
模擬服務(wù)
服務(wù)和自定義的異常:BadProcessingException
@Service
public class ExternalService {
private int count = 0;
public String callService() {
count++;
if (count <= 10) {
return "success";
} else {
throw new BadProcessingException("request fail.");
}
}
}
class BadProcessingException extends RuntimeException {
public BadProcessingException(String message) {
super(message);
}
}
熔斷器配置和使用
在test方法中配置熔斷器和調(diào)用外部服務(wù)
@SpringBootTest
class CircuitBreakerBasicsTest {
@Autowired
ExternalService externalService;
@Test
void cbService() {
// 創(chuàng)建熔斷器的自定義配置
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
// 修改滑動(dòng)窗口大小
.slidingWindowSize(10)
// 修改從開啟狀態(tài)到半開的時(shí)間
.waitDurationInOpenState(Duration.ofSeconds(10)).build();
// 創(chuàng)建熔斷器使用自定義配置
CircuitBreaker circuitBreaker = CircuitBreaker.of("circuitBreaker", config);
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(1000);
System.out.println("count = " + (i + 1));
// 熔斷器執(zhí)行外部服務(wù), 記錄下服務(wù)返回值
String status = circuitBreaker.executeSupplier(externalService::callService);
System.out.println(status);
} catch (Exception e) {
// 打印異常的全限定名稱
System.err.println(e.getClass().getName());
} finally {
// 打印執(zhí)行過程中熔斷器具體的信息
System.out.println("Successful call count: " + circuitBreaker.getMetrics().getNumberOfSuccessfulCalls()
+ " | failed call count: " + circuitBreaker.getMetrics().getNumberOfFailedCalls()
+ " | failure rate %:" + circuitBreaker.getMetrics().getFailureRate()
+ " | state: " + circuitBreaker.getState());
System.out.println("-----------------------------------------------------------------------------");
}
}
}
}
輸出結(jié)果
- 前10次調(diào)用返回成功菩帝,熔斷器處于關(guān)閉狀態(tài),滑動(dòng)窗口中成功的調(diào)用次數(shù)累積到10次
- 11-15次調(diào)用返回自定義的異常BadProcessingException茬腿,滑動(dòng)窗口中失敗的調(diào)用次數(shù)開始增加呼奢,成功的次數(shù)減少,在第15次調(diào)用時(shí)達(dá)到了50%的失敗閾值切平,熔斷器開啟
- 16-24次調(diào)用返回的是熔斷器的異常CallNotPermittedException握础,調(diào)用被拒絕,滑動(dòng)窗口中的調(diào)用次數(shù)不會(huì)變化
- 第25次調(diào)用悴品,此時(shí)經(jīng)過了10s熔斷器從開啟轉(zhuǎn)為了半開狀態(tài)禀综,熔斷器將接受10次調(diào)用,可以看到返回的異常是BadProcessingException
- 之后10次調(diào)用全部失敗苔严,熔斷器又轉(zhuǎn)為開啟狀態(tài)定枷,之后一直在開啟和半開之間轉(zhuǎn)換
輸出結(jié)果太長(zhǎng),只貼出其中的一部分
count = 10
success
Successful call count: 10 | failed call count: 0 | failure rate %:0.0 | state: CLOSED
-----------------------------------------------------------------------------
count = 11
io.ysh.eurekacientconsumer.service.BadProcessingException
Successful call count: 9 | failed call count: 1 | failure rate %:10.0 | state: CLOSED
-----------------------------------------------------------------------------
count = 12
io.ysh.eurekacientconsumer.service.BadProcessingException
Successful call count: 8 | failed call count: 2 | failure rate %:20.0 | state: CLOSED
-----------------------------------------------------------------------------
count = 13
io.ysh.eurekacientconsumer.service.BadProcessingException
Successful call count: 7 | failed call count: 3 | failure rate %:30.0 | state: CLOSED
-----------------------------------------------------------------------------
count = 14
io.ysh.eurekacientconsumer.service.BadProcessingException
Successful call count: 6 | failed call count: 4 | failure rate %:40.0 | state: CLOSED
-----------------------------------------------------------------------------
count = 15
io.ysh.eurekacientconsumer.service.BadProcessingException
Successful call count: 5 | failed call count: 5 | failure rate %:50.0 | state: OPEN
-----------------------------------------------------------------------------
count = 16
io.github.resilience4j.circuitbreaker.CallNotPermittedException
Successful call count: 5 | failed call count: 5 | failure rate %:50.0 | state: OPEN
-----------------------------------------------------------------------------
count = 17
io.github.resilience4j.circuitbreaker.CallNotPermittedException
Successful call count: 5 | failed call count: 5 | failure rate %:50.0 | state: OPEN
-----------------------------------------------------------------------------
count = 18
io.github.resilience4j.circuitbreaker.CallNotPermittedException
Successful call count: 5 | failed call count: 5 | failure rate %:50.0 | state: OPEN
-----------------------------------------------------------------------------
count = 19
io.github.resilience4j.circuitbreaker.CallNotPermittedException
Successful call count: 5 | failed call count: 5 | failure rate %:50.0 | state: OPEN
-----------------------------------------------------------------------------
count = 20
io.github.resilience4j.circuitbreaker.CallNotPermittedException
Successful call count: 5 | failed call count: 5 | failure rate %:50.0 | state: OPEN
-----------------------------------------------------------------------------
count = 21
io.github.resilience4j.circuitbreaker.CallNotPermittedException
Successful call count: 5 | failed call count: 5 | failure rate %:50.0 | state: OPEN
-----------------------------------------------------------------------------
count = 22
io.github.resilience4j.circuitbreaker.CallNotPermittedException
Successful call count: 5 | failed call count: 5 | failure rate %:50.0 | state: OPEN
-----------------------------------------------------------------------------
count = 23
io.github.resilience4j.circuitbreaker.CallNotPermittedException
Successful call count: 5 | failed call count: 5 | failure rate %:50.0 | state: OPEN
-----------------------------------------------------------------------------
count = 24
io.github.resilience4j.circuitbreaker.CallNotPermittedException
Successful call count: 5 | failed call count: 5 | failure rate %:50.0 | state: OPEN
-----------------------------------------------------------------------------
count = 25
io.ysh.eurekacientconsumer.service.BadProcessingException
Successful call count: 0 | failed call count: 1 | failure rate %:-1.0 | state: HALF_OPEN
-----------------------------------------------------------------------------
count = 26
io.ysh.eurekacientconsumer.service.BadProcessingException
Successful call count: 0 | failed call count: 2 | failure rate %:-1.0 | state: HALF_OPEN
-----------------------------------------------------------------------------
springboot 自動(dòng)配置
配置文件+注解使用熔斷器
在application.yml文件中加入如下配置
- configs中是熔斷器配置列表届氢,可以配置多個(gè)熔斷器配置依鸥,default是其中一個(gè)
- instans中是熔斷器的名稱,可以配置多個(gè)悼沈,用于注解中標(biāo)注service使用的是哪個(gè)熔斷器贱迟,baseConfig指定了使用的配置名稱,就是configs中的絮供;也可以指定配置項(xiàng)衣吠,會(huì)覆蓋configs中的同名配置項(xiàng)
- Predicate和Exception需要配置為全限定名稱,Predicate只能配置一個(gè)壤靶,Exceptions可以配置多個(gè)缚俏,按列表的形式配置;測(cè)試代碼中沒有使用到贮乳,這里只是展示下怎樣配置
resilience4j.circuitbreaker:
configs:
default:
slidingWindowSize: 10
waitDurationInOpenState: 10s
failureRateThreshold: 50
instances:
externalService:
baseConfig: default
permittedNumberOfCallsInHalfOpenState: 10
recordFailurePredicate: io.github.robwin.exception.RecordFailurePredicate
recordExceptions:
- org.springframework.web.client.HttpServerErrorException
- java.util.concurrent.TimeoutException
- java.io.IOException
在service方法上使用@CircuitBreaker
注解
- name:指定熔斷器的名稱忧换,就是配置文件中instances下的
externalService
- fallbackMethod:熔斷器開啟時(shí),service拒絕調(diào)用向拆,會(huì)調(diào)用fallback方法返回信息
@CircuitBreaker(name = "externalService", fallbackMethod = "fallback")
public String callService() {
count++;
if (count <= 10) {
return "success";
} else {
throw new BadProcessingException("request fail.");
}
}
public String fallback(Throwable throwable) {
return "fallback";
}
在下面的測(cè)試代碼中就不需要自行創(chuàng)建熔斷器了亚茬,需要打印熔斷器具體信息的話需要注入 CircuitBreakerRegistry
Bean,使用circuitBreaker
方法傳入熔斷器名稱即可
下面的測(cè)試代碼中沒有顯示的使用熔斷器浓恳,輸出的結(jié)果中原本的異常輸出都變成了fallbackMethod方法的返回刹缝,除此之外都一樣碗暗,將注解中的fallbackMethod去掉輸出結(jié)果就跟之前完全一樣了
@SpringBootTest
class CircuitBreakerBasicsTest {
@Autowired
ExternalService externalService;
@Autowired
CircuitBreakerRegistry circuitBreakerRegistry;
@Test
void cbService() {
// 獲取熔斷器,在externalService方法調(diào)用中沒有也不需要使用這個(gè)顯式的引用梢夯,這個(gè)只用于具體信息的獲取
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("externalService");
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(1000);
System.out.println("count = " + (i + 1));
String status = externalService.callService();
System.out.println(status);
} catch (Exception e) {
// 打印異常的全限定名稱
System.err.println(e.getClass().getName());
} finally {
// 打印執(zhí)行過程中熔斷器具體的信息
System.out.println("Successful call count: " + circuitBreaker.getMetrics().getNumberOfSuccessfulCalls()
+ " | failed call count: " + circuitBreaker.getMetrics().getNumberOfFailedCalls()
+ " | failure rate %:" + circuitBreaker.getMetrics().getFailureRate()
+ " | state: " + circuitBreaker.getState());
System.out.println("-----------------------------------------------------------------------------");
}
}
}
}