一、背景
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提供了以下功能:
- 用
HystrixCommand或HystrixObservableCommand對(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è)HystrixCommand或HystrixObservableCommand包裝遠(yuǎn)程調(diào)用依賴的過程。HystrixCommand和HystrixObservableCommand的區(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)造的HystrixCommand或HystrixObservableCommand中,會(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和斷路器的交互流程以及斷路器的工作流程:
- 當(dāng)執(zhí)行HystrixCommand時(shí),會(huì)調(diào)用斷路器查詢是否允許請求。斷路器會(huì)查詢斷路器是否開啟,若沒開啟,直接返回允許請求;若斷路器已開啟,會(huì)判斷恢復(fù)時(shí)間是否已過,已過允許1個(gè)請求,未過返回不允許請求。(對(duì)應(yīng)2.1的第4步)
- 判斷斷路器是否開啟的方式時(shí),計(jì)算時(shí)間范圍內(nèi)錯(cuò)誤百分比是否超過閾值,如果超過閾值,則返回已開啟。
- 當(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為簡單類名#方法名(字段類型...)。
比如說下面的FeignClient的runsWell的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é)
- Hystrix是一個(gè)用于分布式系統(tǒng)故障隔離和控制的開源庫。它通過斷路器模式、請求緩存、請求合并等功能,提供了彈性和可靠的解決方案。Hystrix能夠保護(hù)并控制對(duì)遠(yuǎn)程服務(wù)的調(diào)用,避免級(jí)聯(lián)故障,并快速失敗并快速恢復(fù)。
- Hystrix支持兩種隔離策略,線程池隔離適合遠(yuǎn)程調(diào)用耗時(shí)比較長的場景,但會(huì)有一定的性能損耗;信號(hào)量適合低耗時(shí)、對(duì)性能敏感的場景,缺點(diǎn)是不支持異步調(diào)用、不支持中斷。
- 可以通過
hystrix-core使用原生的Hystrix,也可以通過Spring Cloud OpenFeign或者Spring Cloud Netflix Hystrix使用。 - 可以根據(jù)實(shí)際情況修改Hystrix的配置,可以參考常見的幾種模式使用HystrixCommand。