springcloud5-resilience4j 熔斷器

簡(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租谈。

演示使用

  1. 創(chuàng)建一個(gè)模擬的外部服務(wù)篮奄,前10次調(diào)用成功捆愁,后面的調(diào)用返回異常
  2. 自定義CircuitBreakerConfig,修改滑動(dòng)窗口大小為10窟却,默認(rèn)情況下50%的調(diào)用失敗會(huì)進(jìn)入開啟狀態(tài)昼丑,修改熔斷器從開啟到半開啟時(shí)間為10s
  3. 每隔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("-----------------------------------------------------------------------------");
            }
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末言疗,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子颂砸,更是在濱河造成了極大的恐慌噪奄,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件人乓,死亡現(xiàn)場(chǎng)離奇詭異勤篮,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)撒蟀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)温鸽,“玉大人保屯,你說(shuō)我怎么就攤上這事〉拥妫” “怎么了姑尺?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蝠猬。 經(jīng)常有香客問我切蟋,道長(zhǎng),這世上最難降的妖魔是什么榆芦? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任柄粹,我火速辦了婚禮,結(jié)果婚禮上匆绣,老公的妹妹穿的比我還像新娘驻右。我一直安慰自己,他們只是感情好崎淳,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布堪夭。 她就那樣靜靜地躺著,像睡著了一般拣凹。 火紅的嫁衣襯著肌膚如雪森爽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天嚣镜,我揣著相機(jī)與錄音爬迟,去河邊找鬼。 笑死菊匿,一個(gè)胖子當(dāng)著我的面吹牛雕旨,可吹牛的內(nèi)容都是我干的扮匠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼凡涩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼棒搜!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起活箕,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤力麸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后育韩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體克蚂,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年筋讨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了埃叭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡悉罕,死狀恐怖赤屋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情壁袄,我是刑警寧澤类早,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站嗜逻,受9級(jí)特大地震影響涩僻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜栈顷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一逆日、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧萄凤,春花似錦屏富、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至颤难,卻和暖如春神年,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背行嗤。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工已日, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人栅屏。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓飘千,卻偏偏與公主長(zhǎng)得像堂鲜,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子护奈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容