Hystrix使用及原理概述

一、背景

1. 當(dāng)前問題

一個(gè)系統(tǒng),所有請求共用同一個(gè)APP容器(Tomcat/jetty/等),共用一個(gè)用戶線程池,依賴多個(gè)不同的遠(yuǎn)程服務(wù)。

當(dāng)系統(tǒng)健康時(shí),處理請求的延時(shí)較低,服務(wù)正常運(yùn)行;當(dāng)某個(gè)后端依賴項(xiàng)變得延遲,會(huì)導(dǎo)致處理該請求的用戶線程長時(shí)間阻塞。在流量較低時(shí),只會(huì)影響請求本身;在高流量的情況下,單個(gè)后端依賴項(xiàng)的延遲可能會(huì)導(dǎo)致服務(wù)的所有用戶線程都在阻塞等待。

這個(gè)問題不僅導(dǎo)致與該后端依賴項(xiàng)有關(guān)的請求沒辦法被正常處理,還會(huì)導(dǎo)致其他請求拿不到所需資源而出現(xiàn)異常,從而導(dǎo)致整個(gè)系統(tǒng)都沒辦法正常運(yùn)行。

對(duì)于上述問題,需要通過手段對(duì)故障、延遲進(jìn)行隔離和管理,以便單個(gè)失敗的依賴項(xiàng)不會(huì)導(dǎo)致整個(gè)應(yīng)用程序或系統(tǒng)崩潰。
[圖片上傳失敗...(image-e0ae56-1702653918210)]

[圖片上傳失敗...(image-5add4f-1702653918210)]

[圖片上傳失敗...(image-6eecb5-1702653918210)]

2. Hystrix簡介

Hystrix是Netflix開源的容錯(cuò)庫,旨在處理分布式系統(tǒng)中的故障和延遲。它通過實(shí)現(xiàn)斷路器模式、請求緩存、請求合并等功能,提供了彈性和可靠的解決方案。Hystrix能夠在服務(wù)之間進(jìn)行隔離,當(dāng)一個(gè)服務(wù)出現(xiàn)故障時(shí),它可以防止故障的擴(kuò)散,提高系統(tǒng)的可用性和穩(wěn)定性。在微服務(wù)架構(gòu)中,一個(gè)請求需要調(diào)用多個(gè)服務(wù)是非常常見的,較底層的服務(wù)如果出現(xiàn)故障,會(huì)導(dǎo)致連鎖故障。當(dāng)對(duì)特定的服務(wù)的調(diào)用的不可用達(dá)到一個(gè)閾值(Hystrix是5秒20次)斷路器將會(huì)被打開。斷路打開后,可以避免連鎖故障,通過fallback方法快速返回一個(gè)值。

3. Hystrix的目標(biāo)

Hystrix旨在實(shí)現(xiàn)以下目標(biāo):

  • 保護(hù)并控制通過第三方客戶端庫訪問(通常是通過網(wǎng)絡(luò))的依賴項(xiàng)的延遲和故障。
  • 阻止復(fù)雜分布式系統(tǒng)中的級(jí)聯(lián)故障。
  • 快速失敗并快速恢復(fù)。
  • 在可能的情況下回退并優(yōu)雅降級(jí)。
  • 啟用近乎實(shí)時(shí)的監(jiān)控、警報(bào)和操作控制。

4. Hystrix的功能

為了實(shí)現(xiàn)上述目標(biāo),Hystrix提供了以下功能:

  • HystrixCommandHystrixObservableCommand對(duì)象包裝對(duì)所有外部系統(tǒng)(或“依賴項(xiàng)”)的調(diào)用,通常在單獨(dú)的線程中執(zhí)行
  • 自定義調(diào)用依賴項(xiàng)的超時(shí)時(shí)間
  • 為每個(gè)依賴項(xiàng)維護(hù)一個(gè)小的線程池(或信號(hào)量);如果它變滿,將要發(fā)送到該依賴項(xiàng)的請求立即被拒絕,而不是排隊(duì)等待
  • 統(tǒng)計(jì)成功、失?。蛻舳藪伋龅漠惓#?、超時(shí)和線程拒絕的數(shù)量
  • 可通過手動(dòng)或自動(dòng)的方式,開啟斷路器以停止一段時(shí)間內(nèi)對(duì)特定服務(wù)的所有請求;當(dāng)統(tǒng)計(jì)的失敗率超過閾值,斷路器會(huì)自動(dòng)開啟
  • 在請求失敗、被拒絕、超時(shí)或短路時(shí)執(zhí)行fallback邏輯
  • 近實(shí)時(shí)地監(jiān)控指標(biāo)和配置更改

[圖片上傳失敗...(image-e99505-1702653918210)]

二、原理介紹

1. Hystrix執(zhí)行流程

下圖展示了通過Hystrix請求遠(yuǎn)程服務(wù)的流程

[圖片上傳失敗...(image-cd0da2-1702653918210)]

1.1 創(chuàng)建HystrixCommand/HystrixObservableCommand對(duì)象

第1步,創(chuàng)建一個(gè)HystrixCommandHystrixObservableCommand包裝遠(yuǎn)程調(diào)用依賴的過程。HystrixCommandHystrixObservableCommand的區(qū)別后續(xù)介紹。

1.2 執(zhí)行Command

第2步,執(zhí)行第一步創(chuàng)建的命令Command,可以使用以下四種方式:execute()、queue()、observe()、toObservable(),區(qū)別后續(xù)介紹

1.3 判斷是否有請求緩存

第3步,判斷是否有請求緩存,若存在請求緩存則直接將緩存的值返回,請求緩存的用法后續(xù)介紹。

1.4 判斷是否開啟斷路

第4步,判斷斷路器是否開啟,開啟則不能執(zhí)行遠(yuǎn)程調(diào)用的操作,直接到第8步;若未開啟,則到第5步。第4步的斷路器開啟與否,是通過第7步的斷路器健康值判斷的。

1.5 判斷線程池/信號(hào)量是否已滿

第5步,如果與命令關(guān)聯(lián)的線程池和隊(duì)列(或信號(hào)量)已滿,那么Hystrix將不會(huì)執(zhí)行該命令,而是立即將流量路由到(8)獲取Fallback。第5步的結(jié)果會(huì)反饋到第7步的斷路器健康值中。

1.6 執(zhí)行遠(yuǎn)程調(diào)用

第6步,執(zhí)行遠(yuǎn)程調(diào)用的方法。在第1步構(gòu)造的HystrixCommandHystrixObservableCommand中,會(huì)重寫HystrixCommand.run()HystrixObservableCommand.construct(),里面執(zhí)行遠(yuǎn)程調(diào)用過程。

如果執(zhí)行失敗/超時(shí),則會(huì)路由到第8步執(zhí)行fallback方法。

值得注意的是,執(zhí)行超時(shí)時(shí),線程隔離策略下定時(shí)器會(huì)拋出超時(shí)異常,并且通知執(zhí)行任務(wù)的線程中斷;信號(hào)量隔離策略下,定時(shí)器會(huì)拋出超時(shí)異常,但是執(zhí)行任務(wù)的線程會(huì)執(zhí)行到任務(wù)結(jié)束。

1.7 計(jì)算斷路器的健康度

第7步,Hystrix向斷路器報(bào)告成功、失敗、拒絕和超時(shí),斷路器維護(hù)一組滾動(dòng)計(jì)數(shù)器來計(jì)算統(tǒng)計(jì)數(shù)據(jù)。

它使用這些統(tǒng)計(jì)數(shù)據(jù)來確定何時(shí)應(yīng)該斷路,此時(shí)它會(huì)拒絕任何后續(xù)請求,直到恢復(fù)期過去,然后它會(huì)讓會(huì)進(jìn)入半開狀態(tài)。在此狀態(tài)下,下一個(gè)請求將嘗試調(diào)用后端服務(wù)。如果該請求成功,斷路器會(huì)認(rèn)為服務(wù)已經(jīng)恢復(fù)并轉(zhuǎn)回 關(guān)閉 狀態(tài);如果請求仍然失敗,斷路器會(huì)再次回到 打開 狀態(tài),繼續(xù)進(jìn)行短路操作。

1.8 獲取并執(zhí)行fallback

第8步,當(dāng)?shù)?步發(fā)現(xiàn)斷路器打開、第5步線程池/信號(hào)量已滿、第6步執(zhí)行異常/超時(shí),Hystrix就會(huì)嘗試獲取Command中自定義的備用方法getFallback()。建議該方法不依賴任何網(wǎng)絡(luò)連接,而是從內(nèi)存緩存或其他靜態(tài)邏輯中獲取。

如果fallback執(zhí)行異?;蛘邲]有重寫getFallback()方法,則會(huì)拋出異常。

1.9 返回成功的響應(yīng)

如果執(zhí)行成功,則會(huì)將遠(yuǎn)程調(diào)用的結(jié)果返回給調(diào)用者。

2. 斷路器工作流程

下圖展示了HystrixCommand和斷路器的交互流程以及斷路器的工作流程:

  1. 當(dāng)執(zhí)行HystrixCommand時(shí),會(huì)調(diào)用斷路器查詢是否允許請求。斷路器會(huì)查詢斷路器是否開啟,若沒開啟,直接返回允許請求;若斷路器已開啟,會(huì)判斷恢復(fù)時(shí)間是否已過,已過允許1個(gè)請求,未過返回不允許請求。(對(duì)應(yīng)2.1的第4步)
  2. 判斷斷路器是否開啟的方式時(shí),計(jì)算時(shí)間范圍內(nèi)錯(cuò)誤百分比是否超過閾值,如果超過閾值,則返回已開啟。
  3. 當(dāng)執(zhí)行HystrixCommand之后,會(huì)將結(jié)果反饋給斷路器,以更新斷路器的健康度。

[圖片上傳失敗...(image-ff5521-1702653918210)]

3、 隔離策略

Hystrix 使用艙壁模式來隔離依賴關(guān)系,并限制對(duì)任何單一依賴關(guān)系的并發(fā)訪問。

[圖片上傳失敗...(image-dfbf77-1702653918210)]

3.1 線程池隔離(THREAD)

在此策略中,每個(gè)服務(wù)調(diào)用都在獨(dú)立的線程中執(zhí)行。線程隔離可以防止服務(wù)調(diào)用時(shí)間過長,導(dǎo)致其他服務(wù)調(diào)用受到影響。當(dāng)一個(gè)服務(wù)調(diào)用超時(shí)或發(fā)生故障時(shí),它不會(huì)影響到其他服務(wù)調(diào)用。線程隔離可以確保服務(wù)之間的獨(dú)立性,提高系統(tǒng)的穩(wěn)定性。

[圖片上傳失敗...(image-1f1e5a-1702653918210)]

[圖片上傳失敗...(image-2b070b-1702653918210)]

3.1.1 優(yōu)勢

  • 該應(yīng)用程序完全不受失控客戶端庫的影響。給定依賴庫的池可以填滿,而不會(huì)影響應(yīng)用程序的其余部分。
  • 支持任務(wù)排隊(duì)
  • 支持超時(shí)中斷
  • 支持異步調(diào)用

3.1.2 劣勢

  • 線程調(diào)用會(huì)產(chǎn)生額外的開銷

3.1.3 適用場景

  • 對(duì)于可能耗時(shí)較長、網(wǎng)絡(luò)延遲較高的外部服務(wù)調(diào)用。
  • 當(dāng)需要確保每個(gè)命令在獨(dú)立的線程上運(yùn)行以防止阻塞主線程。
  • 為了防止故障傳播和資源耗盡,需要對(duì)每個(gè)命令進(jìn)行嚴(yán)格的資源限制。
  • 能夠承受一定的線程創(chuàng)建和銷毀的開銷,以換取更穩(wěn)定的系統(tǒng)行為。
  • 適用于處理高負(fù)載和突發(fā)流量的情況,因?yàn)榫€程池可以幫助穩(wěn)定系統(tǒng)的整體性能。

3.2 信號(hào)量隔離(SEMAPHORE)

信號(hào)量隔離是一種基于計(jì)數(shù)器的輕量級(jí)隔離方法,它不創(chuàng)建新的線程。相反,Hystrix 使用一個(gè)指定大小的信號(hào)量來控制并發(fā)訪問的數(shù)量。一旦達(dá)到最大值,任何額外的請求都將被拒絕并觸發(fā)降級(jí)策略。

3.2.1 優(yōu)勢

  • 輕量,無額外開銷

3.2.2 劣勢

  • 不支持任務(wù)排隊(duì)
  • 不支持超時(shí)中斷。執(zhí)行超時(shí),執(zhí)行線程會(huì)繼續(xù)阻塞,直到底層的網(wǎng)絡(luò)調(diào)用超時(shí)
  • 不支持異步調(diào)用。實(shí)際上還是所有請求共用用戶線程,沒辦法完全隔離。

3.2.3 適用場景

  • 對(duì)性能敏感或低延遲要求的應(yīng)用。
  • 當(dāng)調(diào)用的服務(wù)通常是快速響應(yīng)的,例如查詢緩存服務(wù)或者內(nèi)部服務(wù)間通信。
  • 調(diào)用的依賴服務(wù)不需要消耗大量的 CPU 或者 IO 資源。
  • 需要嚴(yán)格控制并發(fā)請求的數(shù)量,但又不希望引入額外的線程開銷。
  • 在某些情況下,可以用于輕量級(jí)的微服務(wù)之間的一致性保證。

三. 使用介紹

本章節(jié)將介紹hystrix提供的原始API、Spring Cloud集成Hystrix的使用

1. Hystrix使用

1.1 依賴

<!-- https://mvnrepository.com/artifact/com.netflix.hystrix/hystrix-core -->
<dependency>
    <groupId>com.netflix.hystrix</groupId>
    <artifactId>hystrix-core</artifactId>
    <version>1.5.18</version>
</dependency>

1.2 Hello World

下面是創(chuàng)建一個(gè)HystrixCommand的方式,繼承HystrixCommand,泛型為遠(yuǎn)程調(diào)用響應(yīng)的類型。

構(gòu)造方法中指定了該命令所屬的分組key,Hystrix會(huì)基于分組key用于報(bào)告、告警、儀表盤、權(quán)限控制等,并且在不指定線程池的情況下,會(huì)根據(jù)這個(gè)key命名線程池。(也就是說,相同key共用同一個(gè)線程池)

run()方法中,是真正執(zhí)行遠(yuǎn)程調(diào)用的位置。

public class CommandHelloWorld extends HystrixCommand<String> {

    private final String name;

    public CommandHelloWorld(String name) {
        // 指定命令分組為ExampleGroup
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
        this.name = name;
    }

    @Override
    protected String run() {
        // 真正執(zhí)行遠(yuǎn)程調(diào)用的位置
        return "Hello " + name + "!";
    }
}

執(zhí)行HystrixCommand,同步等待結(jié)果

String s = new CommandHelloWorld("World").execute();

執(zhí)行HystrixCommand,異步等待結(jié)果。

Future<String> fs = new CommandHelloWorld("World").queue();
String s = fs.get();

響應(yīng)式執(zhí)行HystrixCommand,observe()HystrixCommand轉(zhuǎn)換為Observable,并通過subscribe()設(shè)置訂閱者,設(shè)置當(dāng)處理結(jié)果、處理異常、出現(xiàn)新元素時(shí)的行為。

Observable<String> ho = new CommandHelloWorld("World").observe();
ho.subscribe(new Subscriber<String>() {
    @Override
    public void onCompleted() {
        LOGGER.info("Completed.");
    }
    @Override
    public void onError(Throwable e) {
        LOGGER.error("Error ", e);
    }
    @Override
    public void onNext(String s) {
        // 獲取結(jié)果
        LOGGER.info("Next {}", s);
    }
});

HystrixCommand以外,還可以創(chuàng)建一個(gè) HystrixObservableCommand。這是一種專門用于包裝 Observables 的 HystrixCommand 版本。HystrixObservableCommand 可以包裝發(fā)射(emit)多個(gè)元素的 Observable,而普通的 HystrixCommands(即使轉(zhuǎn)換為 Observable)也永遠(yuǎn)不會(huì)發(fā)射(emit)超過一個(gè)元素。

1.3 Fallback

可以通過實(shí)現(xiàn)Fallback方法,當(dāng)執(zhí)行任務(wù)出現(xiàn)超時(shí)/異常時(shí)優(yōu)雅地降級(jí),返回一個(gè)默認(rèn)值或用于表示錯(cuò)誤的值。

對(duì)于HystrixCommand,需要實(shí)現(xiàn)getFallback()方法, 當(dāng)出現(xiàn)失敗、超時(shí)、線程池/信號(hào)量拒絕、斷路器開啟時(shí),會(huì)執(zhí)行getFallback()方法。對(duì)于調(diào)用方,最終會(huì)收到getFallback()的出參。

public class CommandHelloFailure extends HystrixCommand<String> {

    private final String name;

    public CommandHelloFailure(String name) {
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
        this.name = name;
    }

    @Override
    protected String run() {
        throw new RuntimeException("this command always fails");
    }

    @Override
    protected String getFallback() {
        return "Hello Failure " + name + "!";
    }
}

在沒有實(shí)現(xiàn)getFallback()時(shí),HystrixCommand會(huì)拋出HystrixRuntimeException給調(diào)用方。

無論實(shí)現(xiàn)getFallback()與否,只要失敗都會(huì)在斷路器中計(jì)入失敗。

值得注意的是,如果想在出現(xiàn)錯(cuò)誤之后不執(zhí)行getFallback(),可以拋出HystrixBadRequestException

1.4 Command Key

通過HystrixCommandKey標(biāo)識(shí)HystrixCommand。當(dāng)不設(shè)置是默認(rèn)為類名getClass().getSimpleName();

public CommandHelloWorld(String name) {
    super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
          .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld")));
    this.name = name;
}

1.5 線程池

可以通過HystrixThreadPoolKey指定線程池的名稱,否則默認(rèn)使用HystrixCommandGroupKey

使用HystrixThreadPoolKey而不是單純使用HystrixCommandGroupKey的原因可能是有一些HystrixCommand確實(shí)是在權(quán)限或者邏輯功能上是同一個(gè)組的,但是需要互相隔離。

    public CommandHelloWorld(String name) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
                .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"))
           .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("HelloWorldPool")));// 指定線程池的名稱為HelloWorldPool
        this.name = name;
    }

1.5 隔離策略

通過以下的方式可以指定隔離策略

protected CommandIsolationStrategy(String name) {
    super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
          .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                                     .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD))
          //.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE))
         );
    this.name = name;
}

其他參數(shù)配置與上述方式基本一致,在此不再贅述

1.6 配置

每個(gè)配置參數(shù),有四個(gè)優(yōu)先級(jí),越往后的優(yōu)先級(jí)越高。

1.6.1 代碼全局默認(rèn)值

Hystrix自帶的配置參數(shù)的默認(rèn)值

1.6.2 動(dòng)態(tài)配置全局默認(rèn)值

可修改Java系統(tǒng)參數(shù)修改默認(rèn)值。通過代碼設(shè)置或者在啟動(dòng)服務(wù)時(shí)指定

System.setProperty("hystrix.command.default.execution.isolation.strategy", "SEMAPHORE");
java -Dhystrix.command.default.execution.isolation.strategy=SEMAPHORE ...

其中,default 說明是全局默認(rèn)值

1.6.3 代碼設(shè)置單體值

即上述幾個(gè)小節(jié)在創(chuàng)建HystrixCommand的設(shè)置方式

1.6.4 動(dòng)態(tài)配置單體值

與3.1.7.2一樣,通過修改系統(tǒng)參數(shù)實(shí)現(xiàn)。

System.setProperty("hystrix.command.HystrixCommandKey.execution.isolation.strategy", "THREAD");
java -Dhystrix.command.HystrixCommandKey.execution.isolation.strategy=THREAD ...

其中,HystrixCommandKey修改為HystrixCommand的對(duì)應(yīng)值

更多參數(shù)有關(guān)的內(nèi)容,見第4章

2. Spring Cloud使用

本小節(jié)將介紹Hystrix在Spring Cloud中如何使用。主要介紹兩種方式,Spring Cloud OpenFeign 集成Hystrix、Spring Cloud Netflix Hystrix

2.1 Spring Cloud OpenFeign集成Hystrix

本小節(jié)僅介紹簡單的使用,若想了解OpenFeign集成Hystrix的原理,可參考源碼feign.hystrix.HystrixInvocationHandler(從源碼中可以看到,其實(shí)就是通過動(dòng)態(tài)代理的方式,用HystrixCommand將FeignClient遠(yuǎn)程調(diào)用的方法包裝起來)

2.1.1 依賴

只需引入spring-cloud-starter-openfeign即可。

<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

我們點(diǎn)擊進(jìn)spring-cloud-starter-openfeign的pom文件中可以看到它已引入feign-hystrix,它將Hystrix集成到Feign中。

    <dependency>
      <groupId>io.github.openfeign</groupId>
      <artifactId>feign-hystrix</artifactId>
      <version>10.10.1</version>
      <scope>compile</scope>
    </dependency>

(這個(gè)依賴不需要單獨(dú)引)

2.1.2 配置

首先,劃重點(diǎn),配置必須開啟feign-hystrix,否則hystrix默認(rèn)是不開啟的。詳見OpenFeign源碼org.springframework.cloud.openfeign.FeignClientsConfiguration.HystrixFeignConfiguration

feign:
  hystrix:
    enabled: true

其次,如果需要修改Hystrix的配置參數(shù),可通過修改配置實(shí)現(xiàn)。比如,修改默認(rèn)隔離策略:

hystrix:
  command:
    default:
      execution:
        isolation:
          strategy: THREAD

key和value與Hystrix原生的保持一致,詳見第4章。

需要說明的是,feignClient的每一個(gè)方法都是一個(gè)HystrixCommand,HystrixCommandKey為簡單類名#方法名(字段類型...)。

比如說下面的FeignClientrunsWell的HystrixCommandKey為HystrixService#runsWell(Integer)runs的HystrixCommandKey為HystrixService#runs()

因此,根據(jù)第4章介紹的配置方法,如果想細(xì)粒度地修改指定的HystrixCommand的配置,需要指定對(duì)應(yīng)的HystrixCommandKey。

hystrix:
  command:
    HystrixService#runsWell(Integer):
      execution:
        isolation:
          strategy: SEMAPHORE
    HystrixService#runs():
      execution:
        isolation:
          strategy: THREAD
@FeignClient(value = "HystrixCommandGroupKey")
public interface ExampleApi {
  @GetMapping("/hystrix/ok/{id}")
  String runsWell(@PathVariable("id") Integer id);
    
  @GetMapping("/hystrix/ok")
  String runs();
}

另外,還有一個(gè)點(diǎn)值得提一下,@FeignClient上可以通過name/value指定遠(yuǎn)程RPC的服務(wù),同時(shí)會(huì)將它作為HystrixCommandGroupKey,也就是說在不特殊配置的情況下,無論創(chuàng)建多少個(gè)相同服務(wù)的FeignClient,它們都會(huì)在同一個(gè)分組下,并且會(huì)使用相同的線程池。

2.1.3 Fallback

Fallback比較簡單,在@FeignClient上指定fallbackFactory,并且實(shí)現(xiàn)一個(gè)FallbackFactory即可。

@FeignClient(name = "base-cloud-for-biz", url = "${base-cloud.url}",
        fallbackFactory = BaseCloudClientForBizFallbackFactory.class)
public interface BaseCloudClientForBiz {
    @PostMapping(path="/api/v2/internal/sendSMSWithoutRegister",
            produces = MediaType.APPLICATION_JSON_VALUE,
            consumes = MediaType.APPLICATION_JSON_VALUE)
    ApiResult sendSmsWithoutRegister(SendSmsWithoutRegisterDto dto);
}
@Slf4j
@Component
public class BaseCloudClientForBizFallbackFactory implements FallbackFactory<BaseCloudClientForBiz> {

    private static final String ERROR_LOG = "internal service api is unavailable, api = {}, request data : {}.";

    @Override
    public BaseCloudClientForBiz create(Throwable throwable) {
        return new BaseCloudClientForBiz() {
            @Override
            public ApiResult sendSmsWithoutRegister(SendSmsWithoutRegisterDto dto) {
                log.warn(ERROR_LOG, "sendSmsWithoutRegister", dto);
                return ApiResult.SYSTEM_INTERNAL_ERROR;
            }
        };
    }
}

2.2 Spring Cloud Netflix Hystrix

除了OpenFeign之外,可能還會(huì)有其他調(diào)用遠(yuǎn)程依賴的方法,我們可以通過spring-cloud-starter-netflix-hystrix 引入Hystrix。它支持我們通過注解的方式實(shí)現(xiàn)HystrixCommand的創(chuàng)建、配置。實(shí)現(xiàn)原理可參考com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect(簡單來說就是通過AOP包裝遠(yuǎn)程調(diào)用的方法)

2.2.1 依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

2.2.2 啟動(dòng)

需要在配置類上加上注解EnableHystrix

@EnableHystrix
public class Application {
}

2.2.3 使用及配置

使用比較簡單,在方法上添加注解@HystrixCommand即可

  @HystrixCommand(fallbackMethod = "exampleFallback",
          commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")})
  @GetMapping("/example")
  public String example() {
      return "";
  }

  public String exampleFallback() {
    return "";
  }

可以看出,字段與3.1的用法基本相同

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface HystrixCommand {

    String groupKey() default "";

    String commandKey() default "";

    String threadPoolKey() default "";

    String fallbackMethod() default "";

    HystrixProperty[] commandProperties() default {};

    HystrixProperty[] threadPoolProperties() default {};

    Class<? extends Throwable>[] ignoreExceptions() default {};

    ObservableExecutionMode observableExecutionMode() default ObservableExecutionMode.EAGER;

    HystrixException[] raiseHystrixExceptions() default {};

    String defaultFallback() default "";
}

四、參數(shù)配置

1. Execution

配置 參數(shù)名 默認(rèn)值 備注
隔離策略 hystrix.command.default.execution.isolation.strategy hystrix.command.HystrixCommandKey.execution.isolation.strategy THREAD 可選:THREAD, SEMAPHORE
命令執(zhí)行超時(shí)時(shí)間(毫秒) hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds hystrix.command.HystrixCommandKey.execution.isolation.thread.timeoutInMilliseconds 1000 execution.timeout.enabled=true生效
執(zhí)行超時(shí)開關(guān) hystrix.command.default.execution.timeout.enabled hystrix.command.HystrixCommandKey.execution.timeout.enabled true
超時(shí)中斷開關(guān) hystrix.command.default.execution.isolation.thread.interruptOnTimeout hystrix.command.HystrixCommandKey.execution.isolation.thread.interruptOnTimeout true
信號(hào)量最高并發(fā)請求數(shù) hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests hystrix.command.HystrixCommandKey.execution.isolation.semaphore.maxConcurrentRequests 10

2. Fallback

配置 參數(shù)名 默認(rèn)值 備注
fallback開關(guān) hystrix.command.default.fallback.enabled hystrix.command.HystrixCommandKey.fallback.enabled true

3. Circuit Breaker

配置 參數(shù)名 默認(rèn)值 備注
斷路器開關(guān) hystrix.command.default.circuitBreaker.enabled hystrix.command.HystrixCommandKey.circuitBreaker.enabled true
斷路最小閾值 hystrix.command.default.circuitBreaker.requestVolumeThreshold hystrix.command.HystrixCommandKey.circuitBreaker.requestVolumeThreshold 20 只有滑動(dòng)時(shí)間窗口內(nèi)的請求數(shù)超過閾值,才會(huì)有機(jī)會(huì)觸發(fā)斷路
斷路睡眠時(shí)間(毫秒) hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds hystrix.command.HystrixCommandKey.circuitBreaker.sleepWindowInMilliseconds 5000 斷路之后的睡眠時(shí)長,在此時(shí)間內(nèi)拒絕任何請求
斷路錯(cuò)誤率閾值 hystrix.command.default.circuitBreaker.errorThresholdPercentage hystrix.command.HystrixCommandKey.circuitBreaker.errorThresholdPercentage 50 錯(cuò)誤率超過閾值觸發(fā)斷路

4. Metrics

配置 參數(shù)名 默認(rèn)值 備注
滑動(dòng)統(tǒng)計(jì)時(shí)間長度(毫秒) hystrix.command.default.metrics.rollingStats.timeInMilliseconds hystrix.command.HystrixCommandKey.metrics.rollingStats.timeInMilliseconds 10000
滑動(dòng)統(tǒng)計(jì)桶數(shù)量 hystrix.command.default.metrics.rollingStats.numBuckets hystrix.command.HystrixCommandKey.metrics.rollingStats.numBuckets 10 注意:timeInMilliseconds%numBuckets必須等于0
統(tǒng)計(jì)延遲開關(guān) hystrix.command.default.metrics.rollingPercentile.enabled hystrix.command.HystrixCommandKey.metrics.rollingPercentile.enabled true 若設(shè)置為false,則延遲的情況進(jìn)行統(tǒng)計(jì)

5. Thread Pool

配置 參數(shù)名 默認(rèn)值 備注
線程池核心線程數(shù) hystrix.threadpool.default.coreSize hystrix.threadpool.HystrixThreadPoolKey.coreSize 10
線程池最大線程數(shù) hystrix.threadpool.default.maximumSize hystrix.threadpool.HystrixThreadPoolKey.maximumSize 10
線程池隊(duì)列最大長度 hystrix.threadpool.default.maxQueueSize hystrix.threadpool.HystrixThreadPoolKey.maxQueueSize -1 如果設(shè)置-1,則使用SynchronousQueue隊(duì)列 如果設(shè)置正數(shù),則使用LinkedBlockingQueue隊(duì)列
空閑線程存活時(shí)長(分鐘) hystrix.threadpool.default.keepAliveTimeMinutes hystrix.threadpool.HystrixThreadPoolKey.keepAliveTimeMinutes 1

線程池計(jì)算公式:

線程數(shù)=最高峰時(shí)每秒的請求數(shù)量 × 99%命令執(zhí)行時(shí)間 + 喘息空間

[圖片上傳失敗...(image-bca4db-1702653918210)]

五、常見模式

1. Fail Fast

public class CommandThatFailsFast extends HystrixCommand<String> {

    private final boolean throwException;

    public CommandThatFailsFast(boolean throwException) {
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
        this.throwException = throwException;
    }

    @Override
    protected String run() {
        if (throwException) {
            throw new RuntimeException("failure from CommandThatFailsFast");
        } else {
            return "success";
        }
    }

2. Fail Silent

[圖片上傳失敗...(image-ed6598-1702653918210)]

public class CommandThatFailsSilently extends HystrixCommand<String> {

    private final boolean throwException;

    public CommandThatFailsSilently(boolean throwException) {
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
        this.throwException = throwException;
    }

    @Override
    protected String run() {
        if (throwException) {
            throw new RuntimeException("failure from CommandThatFailsFast");
        } else {
            return "success";
        }
    }

    @Override
    protected String getFallback() {
        return null;
    }
}

3. Fallback: Static

[圖片上傳失敗...(image-1b1dc4-1702653918210)]

    @Override
    protected Boolean getFallback() {
        return true;
    }

4. Fallback via Network

[圖片上傳失敗...(image-7d9d25-1702653918210)]

public class CommandWithFallbackViaNetwork extends HystrixCommand<String> {
    private final int id;

    protected CommandWithFallbackViaNetwork(int id) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceX"))
                .andCommandKey(HystrixCommandKey.Factory.asKey("GetValueCommand")));
        this.id = id;
    }

    @Override
    protected String run() {
        //        RemoteServiceXClient.getValue(id);
        throw new RuntimeException("force failure for example");
    }

    @Override
    protected String getFallback() {
        return new FallbackViaNetwork(id).execute();
    }

    private static class FallbackViaNetwork extends HystrixCommand<String> {
        private final int id;

        public FallbackViaNetwork(int id) {
            super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceX"))
                    .andCommandKey(HystrixCommandKey.Factory.asKey("GetValueFallbackCommand"))
                    // use a different threadpool for the fallback command
                    // so saturating the RemoteServiceX pool won't prevent
                    // fallbacks from executing
                    .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("RemoteServiceXFallback")));
            this.id = id;
        }

        @Override
        protected String run() {
            MemCacheClient.getValue(id);
        }

        @Override
        protected String getFallback() {
            // the fallback also failed
            // so this fallback-of-a-fallback will 
            // fail silently and return null
            return null;
        }
    }
}

5. Primary + Secondary with Fallback

[圖片上傳失敗...(image-529ec-1702653918210)]

六、總結(jié)

  1. Hystrix是一個(gè)用于分布式系統(tǒng)故障隔離和控制的開源庫。它通過斷路器模式、請求緩存、請求合并等功能,提供了彈性和可靠的解決方案。Hystrix能夠保護(hù)并控制對(duì)遠(yuǎn)程服務(wù)的調(diào)用,避免級(jí)聯(lián)故障,并快速失敗并快速恢復(fù)。
  2. Hystrix支持兩種隔離策略,線程池隔離適合遠(yuǎn)程調(diào)用耗時(shí)比較長的場景,但會(huì)有一定的性能損耗;信號(hào)量適合低耗時(shí)、對(duì)性能敏感的場景,缺點(diǎn)是不支持異步調(diào)用、不支持中斷。
  3. 可以通過hystrix-core使用原生的Hystrix,也可以通過Spring Cloud OpenFeign或者Spring Cloud Netflix Hystrix使用。
  4. 可以根據(jù)實(shí)際情況修改Hystrix的配置,可以參考常見的幾種模式使用HystrixCommand。

七、參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容