流程圖
下圖顯示了通過Hystrix向服務依賴項請求時發生的情況:
![Spring cloud微服務架構-Hystrix工作原理(微服務故障熔斷)](http://p2.ttnews.xyz/loading.gif)
以下各節將更詳細地說明此流程:
1.構造一個HystrixCommand或HystrixObservableCommand對象
第一步是構造一個HystrixCommand或HystrixObservableCommand對象,以表示您對依賴項提出的請求。向構造函數傳遞發出請求時所需的任何參數。
HystrixCommand如果期望依賴項返回單個響應,則構造一個對象。例如:
HystrixCommand command = new HystrixCommand(arg1, arg2);
HystrixObservableCommand如果期望依賴項返回一個發出響應的Observable,則構造一個對象。例如:
HystrixObservableCommand command = new HystrixObservableCommand(arg1, arg2);
2.執行命令
通過使用Hystrix命令對象的以下四種方法之一,可以執行命令的四種方法(前兩種僅適用於簡單HystrixCommand對象,不適用於HystrixObservableCommand):
- execute() —阻止,然後返回從依賴項接收到的單個響應(或在發生錯誤的情況下引發異常)
- queue()—返回一個Future,您可以從中獲取依賴項的單個響應
- observe()—訂閱,該Observable代表了來自依賴項的響應,並返回了一個Observable複製該源的。Observable
- toObservable()—返回一個Observable,當您訂閱它時,將執行Hystrix命令併發出其響應
K value = command.execute();
FuturefValue = command.queue();
ObservableohValue = command.observe(); //hot observable
ObservableocValue = command.toObservable(); //cold observable
同步調用execute()調用queue().get()。queue()依次調用toObservable().toBlocking().toFuture()。就是說,最終每個實現HystrixCommand都由Observable實現支持,即使是那些旨在返回單個簡單值的命令也是如此
3.響應是否已緩存?
如果為此命令啟用了請求緩存,並且如果對請求的響應在緩存中可用,則該緩存的響應將立即以的形式返回Observable。(請參見下面的“請求緩存”。)
4.電路是否開路?
當您執行該命令時,Hystrix會檢查斷路器,以查看電路是否斷開。
如果電路開路(或“跳閘”),那麼Hystrix將不執行命令,而是將流路由到(8)獲取回退。
如果電路是閉合的,則流程進行到(5),以檢查是否有足夠的容量來運行該命令。
5.線程池/隊列/信號量是否已滿?
如果與該命令關聯的線程池和隊列(或信號量,如果未在線程中運行)已滿,則Hystrix將不執行該命令,但會立即將流路由到(8)獲取回退。
6. HystrixObservableCommand.construct()或HystrixCommand.run()
在這裡,Hystrix通過為此目的編寫的方法(以下之一)調用對依賴項的請求:
- HystrixCommand.run() —返回單個響應或引發異常
- HystrixObservableCommand.construct()—返回一個Observable,它發出響應或發送onError通知
如果run()or construct()方法超出命令的超時值,則該線程將拋出TimeoutException(如果命令本身未在其自己的線程中運行,則該線程將拋出單獨的計時器線程)。在那種情況下,Hystrix通過8路由響應。獲取Fallback,如果該方法沒有取消/中斷,它將丟棄最終的返回值run()或construct()方法。
請注意,沒有辦法強制潛在線程停止工作-Hystrix在JVM上能做的最好的事情就是將其拋出InterruptedException。如果Hystrix封裝的工作不遵守InterruptedExceptions,儘管客戶端已收到TimeoutException,Hystrix線程池中的線程仍將繼續工作。儘管負載已“正確釋放”,但此行為可能會使Hystrix線程池飽和。大多數Java HTTP客戶端庫不解釋InterruptedExceptions。因此,請確保在HTTP客戶端上正確配置連接和讀取/寫入超時。
如果命令沒有引發任何異常並且返回了響應,則Hystrix在執行一些日誌記錄和度量標準報告後將返回此響應。在的情況下run(),Hystrix返回Observable發出單個響應併發出onCompleted通知的;在的情況下construct()蝟返回相同Observable的返回construct()。
7.計算Circuit健康
Hystrix向斷路器報告成功,失敗,拒絕和超時,斷路器保持滾動的一組計算統計信息的計數器。
它使用這些統計信息來確定電路何時應“跳閘”,在此點它會將隨後的所有請求短路,直到經過恢復期為止,在此之後,在首先檢查某些運行狀況檢查之後,它將再次閉合電路。
8.獲取後備
Hystrix嘗試在命令執行失敗時恢復到您的後備狀態:當construct()或引發異常run()(6.),由於電路斷開而使命令短路(4.),命令的線程池和隊列或信號量為最大容量(5.),或命令已超過其超時長度。
編寫後備,以從內存高速緩存或其他靜態邏輯提供通用響應,而無需任何網絡依賴性。如果必須在回退中使用網絡呼叫,則應通過另一個HystrixCommand或進行HystrixObservableCommand。
對於HystrixCommand,要提供回退邏輯,您可以實現HystrixCommand.getFallback(),該邏輯返回單個回退值。
對於HystrixObservableCommand,要提供後備邏輯,您可以實現HystrixObservableCommand.resumeWithFallback()該邏輯,該邏輯返回一個Observable,它可能會發出一個或多個後備值。
如果fallback方法返回響應,則Hystrix將把此響應返回給調用方。對於a HystrixCommand.getFallback(),它將返回一個Observable,它發出從方法返回的值。在這種情況下,HystrixObservableCommand.resumeWithFallback()它將返回從方法返回的相同Observable。
如果尚未為Hystrix命令實現後備方法,或者後備本身引發異常,則Hystrix仍會返回一個Observable,但它不發出任何內容並立即以onError通知終止。通過該onError通知,將導致命令失敗的異常發送回調用方。(實施回退實現可能會失敗,這是一種糟糕的做法。您應該實施回退,以使其不執行任何可能失敗的邏輯。)
後備失敗或不存在的後備結果將因調用Hystrix命令的方式而異:
- execute() -引發異常
- queue()—成功返回Future,但是Future如果get()調用其方法,則會拋出異常
- observe()—返回一個Observable,當您訂閱它時,將通過調用訂閱者的onError方法立即終止
- toObservable()—返回一個Observable,當您訂閱它時,它將通過調用訂閱者的onError方法終止
9.返回成功的回應
如果Hystrix命令成功執行,它將以的形式將一個或多個響應返回給調用方Observable。根據您在上面的步驟2中調用命令的方式,在Observable返回給您之前,可能會對其進行轉換:
![Spring cloud微服務架構-Hystrix工作原理(微服務故障熔斷)](http://p2.ttnews.xyz/loading.gif)
- execute()-獲得Future的相同方式一樣.queue(),然後調用get()在此Future以獲得由所發射的單個值Observable
- queue()—將轉換Observable為,BlockingObservable以便可以將其轉換為Future,然後返回Future
- observe()— Observable立即訂閱並開始執行命令的流程;返回一個Observable,當您subscribe重播時,它會排放和通知
- toObservable()—返回Observable不變;您必須先subscribe執行此命令才能真正開始導致執行命令的流程
順序圖
上述流程可用官方的時序圖來表示
https://design.codelytics.io/hystrix/how-it-works
斷路器
下圖顯示了a HystrixCommand或HystrixObservableCommand與a交互的方式HystrixCircuitBreaker以及其邏輯和決策流程,包括計數器在斷路器中的行為。
電路打開和關閉的精確方式如下:
- 假設電路上的音量達到某個閾值(HystrixCommandProperties.circuitBreakerRequestVolumeThreshold())...
- 並假設誤差百分比超過閾值誤差百分比(HystrixCommandProperties.circuitBreakerErrorThresholdPercentage())...
- 然後,斷路器從轉換CLOSED為OPEN。
- 當它斷開時,它會使針對該斷路器的所有請求短路。
- 經過一段時間(HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds())後,下一個單個請求被允許通過(這是HALF-OPEN狀態)。如果請求失敗,斷路器將OPEN在睡眠窗口期間返回到該狀態。如果請求成功,斷路器將切換到,CLOSED並且1.中的邏輯將再次接管。
隔離
Hystrix使用隔板模式將依賴關係彼此隔離,並限制對其中任何一個的併發訪問。
線程和線程池
客戶端(庫,網絡調用等)在單獨的線程上執行。這樣可以將它們與調用線程(Tomcat線程池)隔離,以便調用者可以“擺脫”花費太長時間的依賴項調用。
Hystrix使用單獨的,每個依賴關係的線程池作為約束任何給定依賴關係的方式,因此對基礎執行的延遲將僅使該池中的可用線程飽和。
您可以在不使用線程池的情況下防止失敗,但這需要信任的客戶端非常迅速地失敗(網絡連接/讀取超時和重試配置),並且始終表現良好。
Netflix在Hystrix的設計中選擇使用線程和線程池來實現隔離的原因很多,其中包括:
- 許多應用程序會針對許多不同團隊開發的數十種不同服務執行數十種(有時甚至超過100種)不同的後端服務調用。
- 每個服務都提供自己的客戶端庫。
- 客戶端庫一直在變化。
- 客戶端庫邏輯可以更改以添加新的網絡調用。
- 客戶端庫可以包含諸如重試,數據解析,緩存(內存中或跨網絡)以及其他此類行為的邏輯。
- 客戶端庫往往是“黑匣子”-用戶對其實現細節,網絡訪問模式,配置默認值等不透明。
- 在實際的幾次生產中斷中,確定為“哦,某些更改並且應該調整屬性”或“客戶端庫更改了其行為”。
- 即使客戶端本身未更改,服務本身也會更改,這可能會影響性能特徵,進而導致客戶端配置無效。
- 傳遞依賴項可能會引入其他意外的客戶端庫,這些客戶端庫可能不是預期的,也可能配置不正確。
- 大多數網絡訪問是同步執行的。
- 故障和延遲也可能在客戶端代碼中發生,而不僅僅是在網絡調用中。
線程池的好處
通過自己線程池中的線程進行隔離的好處是:
- 該應用程序受到完全保護,不受客戶端庫的攻擊。給定依賴庫的池可以填滿,而不會影響應用程序的其餘部分。
- 該應用程序可以接受風險更低的新客戶端庫。如果發生問題,它將隔離到庫中並且不會影響其他所有內容。
- 當發生故障的客戶端再次恢復正常運行時,線程池將被清除,應用程序將立即恢復運行正常的性能,而整個Tomcat容器不堪重負的情況下,恢復時間很長。
- 如果客戶端庫配置錯誤,線程池的運行狀況將迅速證明這一點(通過增加錯誤,延遲,超時,拒絕等),您可以在不影響應用程序功能的情況下進行處理(通常是通過動態屬性實時進行)。 。
- 如果客戶端服務更改了性能特徵(通常會經常出現問題),進而導致需要調整屬性(增加/減少超時,更改重試次數等),則可以通過線程池指標(錯誤,延遲)再次看到該特徵,超時,拒絕),並且可以在不影響其他客戶端,請求或用戶的情況下進行處理。
- 除了隔離優勢之外,擁有專用線程池還可以提供內置的併發性,可以利用這些併發性在同步客戶端庫之上構建異步外觀(類似於Netflix API如何在Hystrix命令之上構建反應性,完全異步的Java API)。 。
簡而言之,線程池提供的隔離允許客戶端庫和子系統性能特徵的不斷變化和動態組合得到優雅處理,而不會造成中斷。
注意:儘管有單獨的線程提供了隔離,但是您的基礎客戶端代碼也應具有超時和/或對線程中斷的響應,因此它不能無限期地阻塞並使Hystrix線程池飽和。
線程池的缺點
線程池的主要缺點是它們增加了計算開銷。每個命令執行都涉及在單獨的線程上運行命令所涉及的隊列,調度和上下文切換。
Netflix在設計此係統時,決定接受此間接費用,以換取其提供的好處,並認為它很小,不會對成本或性能造成重大影響。
線程成本
Hystrix測量在子線程上執行construct()or run()方法時的延遲以及父線程上的總的端到端時間。這樣,您可以看到Hystrix開銷(線程,度量,日誌記錄,斷路器等)的成本。
Netflix API使用線程隔離每天處理10+億次Hystrix Command執行。每個API實例有40多個線程池,每個線程池中有5-20個線程(大多數設置為10)。
下圖表示一個HystrixCommand在單個API實例上以每秒60個請求的速度執行的情況(每個服務器每秒約350個線程執行總數):
在中位數(或更低)處,擁有單獨的線程沒有任何成本。
在第90 個百分位數處,擁有一個單獨的線程要花費3ms的時間。
在第99 個百分位數處,擁有單獨的線程需要9毫秒的時間。但是請注意,成本的增加遠遠小於單獨線程(網絡請求)的執行時間的增加,後者從2躍升至28,而成本從0躍升至9。
此開銷在90 個百分位和更高的對電路,諸如這些已被認為對大多數Netflix的用例的彈性實現的益處可以接受的。
對於包裝延遲非常低的請求的電路(例如那些主要訪問內存緩存的請求),開銷可能會過高,在這種情況下,您可以使用其他方法,例如可嘗試的信號量,儘管它們不允許超時,提供最大的彈性優勢,而沒有開銷。但是,總的來說開銷很小,以至於Netflix實際上通常比這種技術更喜歡使用單獨線程的隔離優勢。
信號量
您可以使用信號量(或計數器)將併發調用的數量限制為任何給定的依賴項,而不是使用線程池/隊列大小。這使Hystrix無需使用線程池就可以減輕負載,但它不允許超時和退出。如果您信任客戶端,並且只希望減少負載,則可以使用這種方法。
HystrixCommand並HystrixObservableCommand在2個地方支持信號燈:
- 後備: Hystrix檢索後備時,總是在調用Tomcat線程上進行。
- 執行:如果將該屬性設置為execution.isolation.strategy,SEMAPHORE則Hystrix將使用信號量而不是線程來限制調用該命令的併發父線程的數量。
您可以通過定義可以執行多少個併發線程的動態屬性來配置信號燈的這兩種用法。您應該使用與調整線程池大小時類似的計算來確定它們的大小(在不到毫秒的時間內返回的內存中調用的性能可以超過5000rps,並且信號量僅為1或2…但默認值為10)。
注意:如果依賴關係被信號量隔離,然後變為潛在狀態,則父線程將保持阻塞狀態,直到基礎網絡調用超時為止。
一旦達到限制,信號燈拒絕將開始,但是填充信號燈的線程無法釋放。
請求摺疊
您可以HystrixCommand在請求摺疊器(HystrixCollapser是抽象父項)的前面加上,可以將多個請求摺疊到單個後端依賴項調用中。
下圖顯示了兩種情況下的線程和網絡連接數:首先是沒有連接,然後是請求摺疊(假設所有連接在較短的時間窗口內(在這種情況下為10ms)是“併發的”)。
為什麼使用請求摺疊?
使用請求摺疊可減少執行併發HystrixCommand執行所需的線程數和網絡連接數。請求摺疊以一種自動化的方式完成,不會強制代碼庫的所有開發人員協調手動的請求批處理。
全局上下文(跨所有Tomcat線程)
理想的摺疊類型是在全局應用程序級別完成的,因此可以將任何Tomcat線程上任何用戶的請求摺疊在一起。
例如,如果將a配置HystrixCommand為在對檢索電影分級的依賴項的請求中支持任何用戶的批處理,則當同一JVM中的任何用戶線程發出這樣的請求時,Hystrix都會將其請求與其他任何請求一起添加到同一摺疊中網絡通話。
請注意,摺疊器會將單個HystrixRequestContext對象傳遞給摺疊的網絡調用,因此下游系統必須處理這種情況才能使其成為有效的選擇。
用戶請求上下文(單個Tomcat線程)
如果將a配置HystrixCommand為僅處理單個用戶的批處理請求,則Hystrix可以摺疊單個Tomcat線程內的請求(請求)。
例如,如果用戶想為300個視頻對象加載書籤,而不是執行300個網絡調用,Hystrix可以將它們全部合併為一個。
對象建模和代碼複雜度
有時,當您創建對對象的使用者具有邏輯意義的對象模型時,這與對象的生產者的有效資源利用並不十分匹配。
例如,給定一個300個視頻對象的列表,對其進行遍歷並調用getSomeAttribute()每個對象是一個顯而易見的對象模型,但是如果天真地實現,則可能導致300個網絡調用全部在彼此之間的毫秒之內進行(並且很可能會佔用資源)。
有一些手動方法可以處理此問題,例如在允許用戶調用之前getSomeAttribute(),要求他們聲明要為其獲取屬性的視頻對象,以便可以全部提取它們。
或者,您可以劃分對象模型,以便用戶必須從一個地方獲取視頻列表,然後從其他地方詢問該視頻列表的屬性。
這些方法可能導致笨拙的API和對象模型與思維模型和使用模式不匹配。當多個開發人員在一個代碼庫上工作時,它們還可能導致簡單的錯誤和效率低下,因為針對一個用例進行的優化可能會因另一個用例的實現和代碼的新路徑而中斷。
通過將摺疊邏輯向下推到Hystrix層,無論如何創建對象模型,以什麼順序進行調用,或者不同的開發人員是否知道正在完成甚至需要進行優化,都無關緊要。
該getSomeAttribute()方法可以放在最合適的位置,並以適合使用模式的任何方式調用,然後摺疊器會自動將調用批量處理到時間窗口中。
請求崩潰的成本是多少?
啟用請求崩潰的代價是在執行實際命令之前增加了等待時間。最大成本是批處理窗口的大小。
如果您有一條命令需要花費5ms的中位數執行時間和10ms的批處理窗口,則在最壞的情況下執行時間可能變為15ms。通常,一個請求不會在打開時就被提交到窗口,因此中值損失是窗口時間的一半,在這種情況下為5ms。
確定此成本是否值得取決於所執行的命令。高延遲命令不會受到少量額外平均延遲的影響。同樣,給定命令的併發量很關鍵:如果很少有超過1或2個請求被一起批處理,那麼付出代價是沒有意義的。實際上,在單線程順序迭代中,摺疊將是主要的性能瓶頸,因為每次迭代將等待10ms的批處理窗口時間。
但是,如果特定命令同時大量使用,並且可以將數十個甚至數百個呼叫分批處理,則由於Hystrix減少了所需的線程數和與之連接的網絡數,因此獲得的吞吐量通常遠遠超過成本。依賴性。
塌方流
請求緩存
HystrixCommand和HystrixObservableCommand實施方式可以定義一個緩存鍵,然後將其用於去重複數據刪除在併發感知方式的請求範圍內的呼叫。
這是一個示例流程,涉及HTTP請求生命週期和兩個在該請求中執行工作的線程:
請求緩存的好處是:
- 不同的代碼路徑可以執行Hystrix命令,而無需擔心重複的工作。
這在大型代碼庫中特別有用,在該代碼庫中,許多開發人員正在實現不同的功能。
例如,所有需要獲取用戶Account對象的代碼的多個路徑都可以這樣請求:
Account account = new UserGetAccount(accountId).execute();
//or
Observable<account> accountObservable = new UserGetAccount(accountId).observe();
/<account>
Hystrix RequestCache將run()一次且僅執行一次底層方法,並且HystrixCommand儘管實例化了不同的實例,但執行的兩個線程將接收相同的數據。
- 在整個請求中,數據檢索是一致的。
而不是每次執行命令時都可能返回不同的值(或回退),而是將第一個響應緩存併為同一請求內的所有後續調用返回。
- 消除重複的線程執行。
由於請求緩存位於construct()或run()方法調用的前面,因此Hystrix可以在導致線程執行之前對重複項進行重複數據刪除。
如果Hystrix沒有實現請求緩存功能,則每個命令都需要自己在constructor run方法中實現它,這會將其放在線程排隊和執行之後。
閱讀更多 蝸牛與小羊 的文章