Hystrix源碼

hystrix是什么?

在微服務場景中,通常會有很多層的服務調用。如果一個底層服務出現問題,故障會被向上傳播給用戶。我們需要一種機制,當底層服務不可用時,可以阻斷故障的傳播。這就是斷路器的作用。他是系統服務穩定性的最后一重保障。

Hystrix是一個用于處理分布式系統的延遲和容錯的開源庫,在分布式系統里,許多依賴不可避免的會調用失敗,比如超時、異常等,Hystrix能夠保證在一個依賴出問題的情況下,不會導致整體服務失敗,避免級聯故障,以提高分布式系統的彈性。

“斷路器”本身是一種開關裝置,當某個服務單元發生故障之后,通過斷路器的故障監控(類似熔斷保險絲),向調用方返回一個符合預期的、可處理的備選響應(FallBack),而不是長時間的等待或者拋出調用方無法處理的異常,這樣就保證了服務調用方的線程不會被長時間、不必要地占用,從而避免了故障在分布式系統中的蔓延,乃至雪崩。

在springcloud中斷路器組件就是Hystrix。Hystrix也是Netflix套件的一部分。他的功能是,當對某個服務的調用在一定的時間內(默認10s,由metrics.rollingStats.timeInMilliseconds配置),有超過一定次數(默認20次,由circuitBreaker.requestVolumeThreshold參數配置)并且失敗率超過一定值(默認50%,由circuitBreaker.errorThresholdPercentage配置),該服務的斷路器會打開。返回一個由開發者設定的fallback,fallback可以是另一個由Hystrix保護的服務調用,也可以是固定的值。fallback也可以設計成鏈式調用,先執行某些邏輯,再返回fallback。

Hystrix設計目標

對來自依賴的延遲和故障進行防護和控制——這些依賴通常都是通過網絡訪問的

阻止故障的連鎖反應

快速失敗并迅速恢復

回退并優雅降級

提供近實時的監控與告警

Hystrix遵循的設計原則

防止任何單獨的依賴耗盡資源(線程)

過載立即切斷并快速失敗,防止排隊

盡可能提供回退以保護用戶免受故障

使用隔離技術(例如隔板,泳道和斷路器模式)來限制任何一個依賴的影響

通過近實時的指標,監控和告警,確保故障被及時發現

通過動態修改配置屬性,確保故障及時恢復

防止整個依賴客戶端執行失敗,而不僅僅是網絡通信

Hystrix如何實現這些設計目標?

使用命令模式將所有對外部服務(或依賴關系)的調用包裝在HystrixCommand或HystrixObservableCommand對象中,并將該對象放在單獨的線程中執行;

每個依賴都維護著一個線程池(或信號量),線程池被耗盡則拒絕請求(而不是讓請求排隊)。

記錄請求成功,失敗,超時和線程拒絕。

服務錯誤百分比超過了閾值,熔斷器開關自動打開,一段時間內停止對該服務的所有請求。

請求失敗,被拒絕,超時或熔斷時執行降級邏輯。

近實時地監控指標和配置的修改。

如何使用

Netflix斷路器是安裝在服務消費者上。我們需要做的是在服務消費者上開啟斷路器并配置。

1),引入依賴

2),打開開關

只需要在啟動類上加上@EnableHystrix注解即可

3),在服務上加上fallback

Hystrix處理流程

Hystrix流程圖如下:

Hystrix整個工作流如下:

1.構造一個?HystrixCommand或HystrixObservableCommand對象,用于封裝請求,并在構造方法配置請求被執行需要的參數;

2.執行命令,Hystrix提供了4種執行命令的方法,后面詳述;

3.判斷是否使用緩存響應請求,若啟用了緩存,且緩存可用,直接使用緩存響應請求。Hystrix支持請求緩存,但需要用戶自定義啟動;

4.判斷熔斷器是否打開,如果打開,跳到第8步;

5.判斷線程池/隊列/信號量是否已滿,已滿則跳到第8步;

6.執行HystrixObservableCommand.construct()或HystrixCommand.run(),如果執行失敗或者超時,跳到第8步;否則,跳到第9步;

7.統計熔斷器監控指標;

8.走Fallback備用邏輯

9.返回請求響應

從流程圖上可知道,第5步線程池/隊列/信號量已滿時,還會執行第7步邏輯,更新熔斷器統計信息,而第6步無論成功與否,都會更新熔斷器統計信息。

執行命令的幾種方法

Hystrix提供了4種執行命令的方法,execute()和queue()?適用于HystrixCommand對象,而observe()和toObservable()適用于HystrixObservableCommand對象。

execute():同步堵塞方式執行run(),只支持接收一個值對象。hystrix會從線程池中取一個線程來執行run(),并等待返回值。

queue():異步非阻塞方式執行run(),只支持接收一個值對象。調用queue()就直接返回一個Future對象。可通過?Future.get()拿到run()的返回結果,但Future.get()是阻塞執行的。若執行成功,Future.get()返回單個返回值。當執行失敗時,如果沒有重寫fallback,Future.get()拋出異常。

observe():事件注冊前執行run()/construct(),支持接收多個值對象,取決于發射源。調用observe()會返回一個hot Observable,也就是說,調用observe()自動觸發執行run()/construct(),無論是否存在訂閱者。

如果繼承的是HystrixCommand,hystrix會從線程池中取一個線程以非阻塞方式執行run();如果繼承的是HystrixObservableCommand,將以調用線程阻塞執行construct()。

observe()使用方法:調用observe()會返回一個Observable對象;調用這個Observable對象的subscribe()方法完成事件注冊,從而獲取結果

toObservable():事件注冊后執行run()/construct(),支持接收多個值對象,取決于發射源。調用toObservable()會返回一個cold?Observable,也就是說,調用toObservable()不會立即觸發執行run()/construct(),必須有訂閱者訂閱Observable時才會執行。

如果繼承的是HystrixCommand,hystrix會從線程池中取一個線程以非阻塞方式執行run(),調用線程不必等待run();如果繼承的是HystrixObservableCommand,將以調用線程堵塞執行construct(),調用線程需等待construct()執行完才能繼續往下走。

toObservable()使用方法:調用toObservable()會返回一個Observable對象;調用這個Observable對象的subscribe()方法完成事件注冊,從而獲取結果

需注意的是,HystrixCommand也支持toObservable()和observe(),但是即使將HystrixCommand轉換成Observable,它也只能發射一個值對象。只有HystrixObservableCommand才支持發射多個值對象。

幾種方法的關系

execute()實際是調用了queue().get()

queue()實際調用了toObservable().toBlocking().toFuture()

observe()實際調用toObservable()獲得一個cold?Observable,再創建一個ReplaySubject對象訂閱Observable,將源Observable轉化為hot Observable。因此調用observe()會自動觸發執行run()/construct()。

Hystrix總是以Observable的形式作為響應返回,不同執行命令的方法只是進行了相應的轉換。

Hystrix源碼

我們在前面的章節提到過,在maven中引入Hystrix的依賴,然后在啟動類上加上@EnableHystrix注解,這個注解它做了什么呢?在EnableHystrix注解中又有一個@EnableCircuitBreaker的注解,在EnableCircuitBreaker中通過Import注解注入了一個叫EnableCircuitBreakerImportSelector的類,這個類實現了SpringFactoryImportSelector,SpringFactoryImportSelector間接繼承了ImportSelector,ImportSelector是spring提供的一種動態注入bean的方式。SpringFactoryImportSelector是Spring Cloud提供的對ImportSelector的一種實現,通過這個類實現了Hystrix的動態插拔。

何謂Hystrix動態插拔?

我們先看一下spring cloud的hystrix報的spring.factory配置類:

如上圖所示,HystrixCircuitBreakerConfiguration的配置類并沒有包含在spring boot自動掃描的EnableAutoConfiguration配置中,而是自定義了一個配置EnableCircuitBreaker,如果交給spring boot管理,則會在項目啟動的時候就會被spring boot自動加載。EnableCircuitBreaker這個配置是在SpringFactoryImportSelector的importSelectors中通過調用SpringFactoriesLoader.loadFactoryNames將HystrixCircuitBreakerConfiguration加載到spring容器中。

總結:在啟動類中加上@EnableHystrix注解,其實就是注入了HystrixCircuitBreakerConfiguration這個類到spring容器中。

在HystrixCircuitBreakerConfiguration注入了一個切面bean:HystrixCommandAspect.

在HystrixCommandAspect中,定義了兩個切點HystrixCommand和HystrixCollapser以及一個環繞通知,HystrixCollapser是用來進行請求合并的。如果同時加了HystrixCommand和HystrixCollapser注解,直接拋出異常;在META_HOLDER_FACTORY_MAP中包含了兩個值,就是HystrixCommand和HystrixCollapser,通過META_HOLDER_FACTORY_MAP.get來獲取加的到底是哪個注解。

熔斷

現實生活中,可能大家都有注意到家庭電路中通常會安裝一個保險盒,當負載過載時,保險盒中的保險絲會自動熔斷,以保護電路及家里的各種電器,這就是熔斷器的一個常見例子。Hystrix中的熔斷器(Circuit Breaker)也是起類似作用,Hystrix在運行過程中會向每個commandKey對應的熔斷器報告成功、失敗、超時和拒絕的狀態,熔斷器維護并統計這些數據,并根據這些統計信息來決策熔斷開關是否打開。如果打開,熔斷后續請求,快速返回。隔一段時間(默認是5s)之后熔斷器嘗試半開,放入一部分流量請求進來,相當于對依賴服務進行一次健康檢查,如果請求成功,熔斷器關閉。

熔斷器配置

Circuit Breaker主要包括如下6個參數:

1、circuitBreaker.enabled

是否啟用熔斷器,默認是TRUE。可以直接關閉,關閉之后返回一個NoOpCircuitBreaker。

2 、circuitBreaker.forceOpen

熔斷器強制打開,始終保持打開狀態,不關注熔斷開關的實際狀態。默認值FLASE。

3、circuitBreaker.forceClosed

熔斷器強制關閉,始終保持關閉狀態,不關注熔斷開關的實際狀態。默認值FLASE。

4、circuitBreaker.errorThresholdPercentage

錯誤率,默認值50%,例如一段時間(10s)內有100個請求,其中有54個超時或者異常,那么這段時間內的錯誤率是54%,大于了默認值50%,這種情況下會觸發熔斷器打開。

5、circuitBreaker.requestVolumeThreshold

默認值20。含義是一段時間內至少有20個請求才進行errorThresholdPercentage計算。比如一段時間了有19個請求,且這些請求全部失敗了,錯誤率是100%,但熔斷器不會打開,總請求數不滿足20。

6、circuitBreaker.sleepWindowInMilliseconds

半開狀態試探睡眠時間,默認值5000ms。如:當熔斷器開啟5000ms之后,會嘗試放過去一部分流量進行試探,確定依賴服務是否恢復。

熔斷器工作原理

下圖展示了HystrixCircuitBreaker的工作原理:

熔斷器工作的詳細過程如下:

第一步,調用allowRequest()判斷是否允許將請求提交到線程池

如果熔斷器強制打開,circuitBreaker.forceOpen為true,不允許放行,返回。

如果熔斷器強制關閉,circuitBreaker.forceClosed為true,允許放行。此外不必關注熔斷器實際狀態,也就是說熔斷器仍然會維護統計數據和開關狀態,只是不生效而已。

第二步,調用isOpen()判斷熔斷器開關是否打開

如果熔斷器開關打開,進入第三步,否則繼續;

如果一個周期內總的請求數小于circuitBreaker.requestVolumeThreshold的值,允許請求放行,否則繼續;

如果一個周期內錯誤率小于circuitBreaker.errorThresholdPercentage的值,允許請求放行。否則,打開熔斷器開關,進入第三步。

第三步,調用allowSingleTest()判斷是否允許單個請求通行,檢查依賴服務是否恢復

如果熔斷器打開,且距離熔斷器打開的時間或上一次試探請求放行的時間超過circuitBreaker.sleepWindowInMilliseconds的值時,熔斷器器進入半開狀態,允許放行一個試探請求;否則,不允許放行。

此外,為了提供決策依據,每個熔斷器默認維護了10個bucket,每秒一個bucket,當新的bucket被創建時,最舊的bucket會被拋棄。其中每個blucket維護了請求成功、失敗、超時、拒絕的計數器,Hystrix負責收集并統計這些計數器。

在獲取熔斷器時,判斷熔斷器是否為空,如果不為空,則說明有緩存;如果為空,則構建一個新的熔斷器。

總結:在程序入口的配置類里面,定義了AOP的切面,通過環繞通知around代理Hystrix的注解,所有加了注解的方法都會進入代理方法中,在代理方法中,會讀取系統的配置、注解的配置,構建一個Command對象,在Command對象構建的中途,會構建熔斷器組件和線程池等等一系列的配置的初始化,配置來源都是yml+注解上面的配置;可以根據Command的key把多個注解綁定起來,這樣就不用重復構建多個Command對象。

資源隔離

資源隔離主要指對線程的隔離。Hystrix提供了兩種線程隔離方式:線程池和信號量。

線程隔離-線程池

Hystrix通過命令模式對發送請求的對象和執行請求的對象進行解耦,將不同類型的業務請求封裝為對應的命令請求。如訂單服務查詢商品,查詢商品請求->商品Command;商品服務查詢庫存,查詢庫存請求->庫存Command。并且為每個類型的Command配置一個線程池,當第一次創建Command時,根據配置創建一個線程池,并放入ConcurrentHashMap,如商品Command:

后續查詢商品的請求創建Command時,將會重用已創建的線程池。線程池隔離之后的服務依賴關系:

通過將發送請求線程與執行請求的線程分離,可有效防止發生級聯故障。當線程池或請求隊列飽和時,Hystrix將拒絕服務,使得請求線程可以快速失敗,從而避免依賴問題擴散。

線程池隔離優缺點

優點:

1.保護應用程序以免受來自依賴故障的影響,指定依賴線程池飽和不會影響應用程序的其余部分。

2.當引入新客戶端lib時,即使發生問題,也是在本lib中,并不會影響到其他內容。

3。當依賴從故障恢復正常時,應用程序會立即恢復正常的性能。

4.當應用程序一些配置參數錯誤時,線程池的運行狀況會很快檢測到這一點(通過增加錯誤,延遲,超時,拒絕等),同時可以通過動態屬性進行實時糾正錯誤的參數配置。

5.如果服務的性能有變化,需要實時調整,比如增加或者減少超時時間,更改重試次數,可以通過線程池指標動態屬性修改,而且不會影響到其他調用請求。

6.除了隔離優勢外,hystrix擁有專門的線程池可提供內置的并發功能,使得可以在同步調用之上構建異步門面(外觀模式),為異步編程提供了支持(Hystrix引入了Rxjava異步框架)。

注意:盡管線程池提供了線程隔離,我們的客戶端底層代碼也必須要有超時設置或響應線程中斷,不能無限制的阻塞以致線程池一直飽和。

缺點:

線程池的主要缺點是增加了計算開銷。每個命令的執行都在單獨的線程完成,增加了排隊、調度和上下文切換的開銷。因此,要使用Hystrix,就必須接受它帶來的開銷,以換取它所提供的好處。

通常情況下,線程池引入的開銷足夠小,不會有重大的成本或性能影響。但對于一些訪問延遲極低的服務,如只依賴內存緩存,線程池引入的開銷就比較明顯了,這時候使用線程池隔離技術就不適合了,我們需要考慮更輕量級的方式,如信號量隔離。

參考文章:https://my.oschina.net/7001/blog/1619842

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,406評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,034評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,413評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,449評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,165評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,559評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,606評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,781評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,327評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,084評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,278評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,849評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,495評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,927評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,172評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,010評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,241評論 2 375

推薦閱讀更多精彩內容