天天看點

從源碼分析Hystrix工作機制

本文從源碼角度分析了Hystrix熔斷、隔離、健康統計等核心子產品的實作原理,加強對底層原理的了解可以更加便捷的使用它。

在複雜的分布式應用中有着許多的依賴,各個依賴都有難免在某個時刻失敗,如果應用不隔離各個依賴,降低外部的風險,那容易拖垮整個應用。

舉個電商場景中常見的例子,比如訂單服務調用了庫存服務、商品服務、積分服務、支付服務,系統均正常情況下,訂單子產品正常運作。

從源碼分析Hystrix工作機制

但是當積分服務發生異常時且會阻塞30s時,訂單服務就有有部分請求失敗,且工作線程阻塞在調用積分服務上。

從源碼分析Hystrix工作機制

流量高峰時,問題會更加嚴重,訂單服務的所有請求都會阻塞在調用積分服務上,工作線程全部挂起,導緻機器資源耗盡,訂單服務也不可用,造成級聯影響,整個叢集當機,這種稱為雪崩效應。

從源碼分析Hystrix工作機制

是以需要一種機制,使得單個服務出現故障時,整個叢集可用性不受到影響。Hystrix就是實作這種機制的架構,下面我們分析一下Hystrix整體的工作機制。

從源碼分析Hystrix工作機制

【入口】Hystrix的執行入口是HystrixCommand或HystrixObservableCommand對象,通常在Spring應用中會通過注解和AOP來實作對象的構造,以降低對業務代碼的侵入性;

【緩存】HystrixCommand對象實際開始執行後,首先是否開啟緩存,若開啟緩存且命中,則直接傳回;

【熔斷】若熔斷器打開,則執行短路,直接走降級邏輯;若熔斷器關閉,繼續下一步,進入隔離邏輯。熔斷器的狀态主要基于視窗期内執行失敗率,若失敗率過高,則熔斷器自動打開;

【隔離】使用者可配置走線程池隔離或信号量隔離,判斷線程池任務已滿(或信号量),則進入降級邏輯;否則繼續下一步,實際由線程池任務線程執行業務調用;

【執行】實際開始執行業務調用,若執行失敗或異常,則進入降級邏輯;若執行成功,則正常傳回;

【逾時】通過定時器延時任務檢測業務調用執行是否逾時,若逾時則取消業務執行的線程,進入降級邏輯;若未逾時,則正常傳回。線程池、信号量兩種政策均隔離方式支援逾時配置(信号量政策存在缺陷);

【降級】進入降級邏輯後,當業務實作了HystrixCommand.getFallback() 方法,則傳回降級處理的資料;當未實作時,則傳回異常;

【統計】業務調用執行結果成功、失敗、逾時等均會進入統計子產品,通過健康統計結果來決定熔斷器打開或關閉。

都說源碼裡沒有秘密,下面我們來分析下核心功能源碼,看看Hystrix如何實作整體的工作機制。

家用電路中都有保險絲,保險絲的作用場景是,當電路發生故障或異常時,伴随着電流不斷升高,并且升高的電流有可能損壞電路中的某些重要器件或貴重器件,也有可能燒毀電路甚至造成火災。

若電路中正确地安置了保險絲,那麼保險絲就會在電流異常升高到一定的高度和一定的時候,自身熔斷切斷電流,進而起到保護電路安全運作的作用。Hystrix提供的熔斷器就有類似功能,應用調用某個服務提供者,當一定時間内請求總數超過配置的門檻值,且視窗期内錯誤率過高,那Hystrix就會對調用請求熔斷,後續的請求直接短路,直接進入降級邏輯,執行本地的降級政策。

Hystrix具有自我調節的能力,熔斷器打開在一定時間後,會嘗試通過一個請求,并根據執行結果調整熔斷器狀态,讓熔斷器在closed,open,half-open三種狀态之間自動切換。

從源碼分析Hystrix工作機制

【HystrixCircuitBreaker】boolean attemptExecution():每次HystrixCommand執行,都要調用這個方法,判斷是否可以繼續執行,若熔斷器狀态為打開且超過休眠視窗,更新熔斷器狀态為half-open;通過CAS原子變更熔斷器狀态來保證隻放過一條業務請求實際調用提供方,并根據執行結果調整狀态。

【HystrixCircuitBreaker】void markSuccess():HystrixCommand執行成功後調用,當熔斷器狀态為half-open,更新熔斷器狀态為closed。此種情況為熔斷器原本為open,放過單條請求實際調用服務提供者,并且後續執行成功,Hystrix自動調節熔斷器為closed。

【HystrixCircuitBreaker】void markNonSuccess():HystrixCommand執行成功後調用,若熔斷器狀态為half-open,更新熔斷器狀态為open。此種情況為熔斷器原本為open,放過單條請求實際調用服務提供者,并且後續執行失敗,Hystrix繼續保持熔斷器打開,并把此次請求作為休眠視窗期開始時間。

【HystrixCircuitBreaker】void subscribeToStream():熔斷器訂閱健康統計結果,若目前請求資料大于一定值且錯誤率大于門檻值,自動更新熔斷器狀态為opened,後續請求短路,不再實際調用服務提供者,直接進入降級邏輯。

在貨船中,為了防止漏水和火災的擴散,一般會将貨倉進行分割,避免了一個貨倉出事導緻整艘船沉沒的悲劇。同樣的,在Hystrix中,也采用了這樣的艙壁模式,将系統中的服務提供者隔離起來,一個服務提供者延遲升高或者失敗,并不會導緻整個系統的失敗,同時也能夠控制調用這些服務的并發度。如下圖,訂單服務調用下遊積分、庫存等服務使用不同的線程池,當積分服務故障時,隻會把對應線程池打滿,而不會影響到其他服務的調用。Hystrix隔離模式支援線程池和信号量兩種方式。

從源碼分析Hystrix工作機制

信号量模式控制單個服務提供者執行并發度,比如單個CommondKey下正在請求數為N,若N小于maxConcurrentRequests,則繼續執行;若大于等于maxConcurrentRequests,則直接拒絕,進入降級邏輯。信号量模式使用請求線程本身執行,沒有線程上下文切換,開銷較小,但逾時機制失效。

【AbstractCommand】ObservableapplyHystrixSemantics(finalAbstractCommand _cmd):嘗試擷取信号量,若能擷取到,則繼續調用服務提供者;若不能擷取到,則進入降級政策。

【AbstractCommand】TryableSemaphore getExecutionSemaphore():擷取信号量執行個體,若目前隔離模式為信号量,則根據commandKey擷取信号量,不存在時初始化并緩存;若目前隔離模式為線程池,則使用預設信号量TryableSemaphoreNoOp.DEFAULT,全部請求可通過。

線程池模式控制單個服務提供者執行并發度,代碼上都會先走擷取信号量,隻是使用預設信号量,全部請求可通過,然後實際調用線程池邏輯。線程池模式下,比如單個CommondKey下正在請求數為N,若N小于maximumPoolSize,會先從 Hystrix 管理的線程池裡面獲得一個線程,然後将參數傳遞給任務線程去執行真正調用,如果并發請求數多于線程池線程個數,就有任務需要進入隊列排隊,但排隊隊列也有上限,如果排隊隊列也滿,則進去降級邏輯。線程池模式可以支援異步調用,支援逾時調用,存線上程切換,開銷大。

【AbstractCommand】ObservableexecuteCommandWithSpecifiedIsolation(final AbstractCommand _cmd):從線程池中擷取線程,并執行,過程中記錄線程狀态。

【HystrixThreadPool】Subscription schedule(final Action0 action):HystrixContextScheduler是Hystrix對rx中Scheduler排程器的重寫,主要為了實作在Observable未被訂閱時,不執行指令,以及支援在指令執行過程中能夠打斷運作。在rx中,Scheduler将生成對應的Worker給Observable用于執行指令,由Worker具體負責相關執行線程的排程,ThreadPoolWorker是Hystrix自行實作的Worker,執行排程的核心方法。

從源碼分析Hystrix工作機制

Hystrix逾時機制降低了第三方依賴項延遲過高對調用方的影響,使請求快速失敗。主要通過延遲任務機制實作,包括注冊延時任務過程和執行延時任務過程。

當隔離政策為線程池時,主線程訂閱執行結果,線程池中任務線程調用提供者服務端,同時會有定時器線程在一定時間後檢測任務是否完成,若未完成則表示任務逾時,抛出逾時異常,并且後續任務線程的執行結果也會跳過不再釋出;若已完成則表示任務在逾時時間内完成執行完成,定時器檢測任務結束。

當隔離政策為信号量時,主線程訂閱執行結果并實際調用提供者服務端(沒有任務線程),當超出指定時間,主線程仍然會執行完業務調用,然後抛出逾時異常。信号量模式下逾時配置有一定缺陷,不能取消在執行的調用,并不能限制主線程傳回時間。

【AbstractCommand】ObservableexecuteCommandAndObserve(finalAbstractCommand _cmd):逾時檢測入口,執行lift(new HystrixObservableTimeoutOperator(_cmd))關聯逾時檢測任務。

【HystrixObservableTimeoutOperator】Subscriber<? super R> call(final Subscriber<? super R> child):建立檢測任務,并關聯延遲任務;若檢測任務執行時仍未執行完成,則抛出逾時異常;若已執行完成或異常,則清除檢測任務。

【HystrixTimer】ReferenceaddTimerListener(finalTimerListener listener):addTimerListener通過java的定時任務服務scheduleAtFixedRate在延遲逾時時間後執行。

public Reference addTimerListener(final TimerListener listener) {//初始化xianstartThreadIfNeeded();//構造檢測任務Runnable r = new Runnable() {

Hystrix降級邏輯作為兜底的政策,當出現業務執行異常、線程池或信号量已滿、執行逾時等情況時,會進入降級邏輯。降級邏輯中應從記憶體或靜态邏輯擷取通用傳回,盡量不依賴依賴網絡調用,如果未實作降級方法或降級方法中也出現異常,則業務線程中會引發異常。

從源碼分析Hystrix工作機制

【AbstractCommand】Observable getFallbackOrThrowException(finalAbstractCommand _cmd, final HystrixEventType eventType, final FailureType failureType, final String message, final Exception originalException):首先判斷是否為不可恢複異常,若是則不走降級邏輯,直接異常傳回;其次判斷是否能擷取到降級信号量,然後走降級邏輯;當降級邏輯中也發生異常或者沒有降級方法實作時,則異常傳回。

【HystrixCommand】R getFallback():HystrixCommand預設抛出操作不支援異常,需要子類覆寫getFalBack方法實作降級邏輯。

Hystrix基于通過滑動視窗的資料統計判定服務失敗占比選擇性熔斷,能夠實作快速失敗并走降級邏輯。步驟如下:

AbstractCommand執行完成後調⽤ handleCommandEnd⽅法将執行結果HystrixCommandCompletion事件釋出到事件流中;

事件流通過 Observable.window()⽅法将事件按時間分組,并通過 flatMap()⽅法将事件按類型(成功、失敗等)聚合成桶,形成桶流;

再将各個桶使⽤Observable.window()按視窗内桶數量聚合成滑動窗⼝資料;

将滑動視窗資料聚合成資料對象(如健康資料流、累計資料等);

熔斷器CircuitBreaker初始化時訂閱健康資料流,根據健康情況修改熔斷器的開關。

從源碼分析Hystrix工作機制

【AbstractCommand】void handleCommandEnd(boolean commandExecutionStarted):在業務執行完畢後,會調用handleCommandEnd方法,在此方法中,上報執行結果executionResult,這也是健康統計的入口。

【BucketedRollingCounterStream】BucketedRollingCounterStream(HystrixEventStream stream, final int numBuckets, int bucketSizeInMs,final Func2<Bucket, Event, Bucket> appendRawEventToBucket,final Func2<Output, Bucket, Output> re-duceBucket)

健康統計類HealthCountsStream的滑動視窗實作主要是在父類BucketedRollingCounterStream,首先父類BucketedCounterStream将事件流處理成桶流,BucketedRollingCounterStream處理成滑動視窗,然後由HealthCountsStream傳入的reduceBucket函數處理成健康統計資訊.

【HealthCounts】HealthCounts plus(long[] eventTypeCounts):對桶内資料按事件類型累計,生成統計資料HealthCounts;

在分布式環境中,不可避免地會有許多服務的依賴項中有的失敗。Hystrix作為一個庫,可通過添加熔斷、隔離、降級等邏輯來幫助使用者控制分布式服務之間的互動,以提高系統的整體彈性。主要功能如下:

保護系統,控制來自通路第三方依賴項(通常是通過網絡)的延遲和失敗

阻止複雜分布式系統中的級聯故障

快速失敗并快速恢複

平滑降級

近乎實時的監控,警報和控制

Hystrix使用過程中,有一些要注意的點:

覆寫的getFallback()方法,盡量不要有網絡依賴。如果有網絡依賴,建議采用多次降級,即在getFallback()内執行個體化 HystrixCommand,并執行Command。getFallback()盡量保證高性能傳回,快速降級。

HystrixCommand 建議采用的是線程隔離政策。

hystrix.threadpool.default.allowMaximumSizeToDivergeFromCoreSize設定為true時,hystrix.threadpool.default.maximumSize才會生效。最大線程數需要根據業務自身情況和性能測試結果來考量,盡量初始時設定小一些,支援動态調整大小,因為它是減少負載并防止資源在延遲發生時被阻塞的主要工具。

信号隔離政策下,執行業務邏輯時,使用的是應用服務的父級線程(如Tomcat容器線程)。是以,一定要設定好并發量,有網絡開銷的調用,不建議使用該政策,容易導緻容器線程排隊堵塞,進而影響整個應用服務。

另外Hystrix高度依賴RxJava這個響應式函數程式設計架構,簡單了解RxJava的使用方式,有利于了解源碼邏輯。

Hystrix Github倉庫:https://github.com/Netflix/Hystrix

分享 vivo 網際網路技術幹貨與沙龍活動,推薦最新行業動态與熱門會議。

繼續閱讀