下面的流程圖展現了當使用Hystrix的依賴請求,Hystrix是如何工做的。java
下面將更詳細的解析每個步驟都發生哪些動做:後端
構建一個HystrixCommand
或者HystrixObservableCommand
對象。緩存
第一步就是構建一個HystrixCommand
或者HystrixObservableCommand
對象,該對象將表明你的一個依賴請求,向構造函數中傳入請求依賴所須要的參數。tomcat
若是構建HystrixCommand
中的依賴返回單個響應,例如:服務器
HystrixCommand command = new HystrixCommand(arg1, arg2);
若是依賴須要返回一個Observable
來發射響應,就須要經過構建HystrixObservableCommand
對象來完 成,例如:網絡
HystrixObservableCommand command = new HystrixObservableCommand(arg1, arg2);
執行命令併發
有4種方式能夠執行一個Hystrix命令。異步
execute()
—該方法是阻塞的,從依賴請求中接收到單個響應(或者出錯時拋出異常)。queue()
—從依賴請求中返回一個包含單個響應的Future對象。observe()
—訂閱一個從依賴請求中返回的表明響應的Observable對象。toObservable()
—返回一個Observable對象,只有當你訂閱它時,它纔會執行Hystrix命令併發射響應。K value = command.execute(); Future<K> fValue = command.queue(); Observable<K> ohValue = command.observe(); //hot observable Observable<K> ocValue = command.toObservable(); //cold observable
同步調用方法execute()
實際上就是調用queue().get()
方法,queue()
方法的調用的是toObservable().toBlocking().toFuture()
.也就是說,最終每個HystrixCommand都是經過Observable來實現的,即便這些命令僅僅是返回一個簡單的單個值。函數
若是這個命令的請求緩存已經開啓,而且本次請求的響應已經存在於緩存中,那麼就會當即返回一個包含緩存響應的Observable
(下面將Request Cache部分將對請求的cache作講解)。性能
當命令執行執行時,Hystrix會檢查迴路器是否被打開。
若是迴路器被打開(或者tripped),那麼Hystrix就不會再執行命名,而是直接路由到第8
步,獲取fallback方法,並執行fallback邏輯。
若是迴路器關閉,那麼將進入第5
步,檢查是否有足夠的容量來執行任務。(其中容量包括線程池的容量,隊列的容量等等)。
若是與該命令相關的線程池或者隊列已經滿了,那麼Hystrix就不會再執行命令,而是當即跳到第8
步,執行fallback邏輯。
HystrixObservableCommand.construct() 或者 HystrixCommand.run()
在這裏,Hystrix經過你寫的方法邏輯來調用對依賴的請求,經過下列之一的調用:
HystrixCommand.run()
—返回單個響應或者拋出異常。HystrixObservableCommand.construct()
—返回一個發射響應的Observable或者發送一個onError()
的通知。若是執行run()
方法或者construct()
方法的執行時間大於命令所設置的超時時間值,那麼該線程將會拋出一個TimeoutException
異常(或者若是該命令沒有運行在它本身的線程中,[or a separate timer thread will, if the command itself is not running in its own thread])。在這種狀況下,Hystrix將會路由到第8
步,執行fallback邏輯,而且若是run()
或者construct()
方法沒有被取消或者中斷,會丟棄這兩個方法最終返回的結果。
請注意,沒有任何方式能夠強制終止一個潛在[latent]的線程的運行,Hystrix可以作的最好的方式是讓JVM拋出一個InterruptedException
異常,若是你的任務被Hystrix所包裝,並不意味着會拋出一個InterruptedExceptions
異常,該線程在Hystrix的線程池內會進行執行,雖然在客戶端已經接收到了TimeoutException
異常,這個行爲可以滲透到Hystrix的線程池中,[though the load is 'correctly shed'],絕大多數的Http Client不會將這一行爲視爲InterruptedExceptions
,因此,請確保正確配置鏈接或者讀取/寫入的超時時間。
若是命令最終返回了響應而且沒有拋出任何異常,Hystrix在返回響應後會執行一些log和指標的上報,若是是調用run()
方法,Hystrix會返回一個Observable,該Observable會發射單個響應而且會調用onCompleted
方法來通知響應的回調,若是是調用construct()
方法,Hystrix會經過construct()
方法返回相同的Observable對象。
Hystrix會報告成功、失敗、拒絕和超時的指標給迴路器,迴路器包含了一系列的滑動窗口數據,並經過該數據進行統計。
它使用這些統計數據來決定迴路器是否應該熔斷,若是須要熔斷,將在必定的時間內不在請求依賴[短路請求](譯者:這必定的時候能夠經過配置指定),當再一次檢查請求的健康的話會從新關閉迴路器。
獲取FallBack
當命令執行失敗時,Hystrix會嘗試執行自定義的Fallback邏輯:
construct()
或者run()
方法執行過程當中拋出異常。寫一個fallback方法,提供一個不須要網絡依賴的通用響應,從內存緩存或者其餘的靜態邏輯獲取數據。若是再fallback內必須須要網絡的調用,更好的作法是使用另外一個HystrixCommand
或者HystrixObservableCommand
。
若是你的命令是繼承自HystrixCommand
,那麼能夠經過實現HystrixCommand.getFallback()
方法返回一個單個的fallback值。
若是你的命令是繼承自HystrixObservableCommand
,那麼能夠經過實現HystrixObservableCommand.resumeWithFallback()
方法返回一個Observable,而且該Observable可以發射出一個fallback值。
Hystrix會把fallback方法返回的響應返回給調用者。
若是你沒有爲你的命令實現fallback方法,那麼當命令拋出異常時,Hystrix仍然會返回一個Observable,可是該Observable並不會發射任何的數據,而且會當即終止並調用onError()
通知。經過這個onError
通知,能夠將形成該命令拋出異常的緣由返回給調用者。
失敗或不存在回退的結果將根據您如何調用Hystrix命令而有所不一樣:
execute()
:拋出一個異常。queue()
:成功返回一個Future,可是若是調用get()方法,將會拋出一個異常。observe()
:返回一個Observable,當你訂閱它時,它將當即終止,並調用onError()方法。toObservable()
:返回一個Observable,當你訂閱它時,它將當即終止,並調用onError()方法。若是Hystrix命令執行成功,它將以Observable形式返回響應給調用者。根據你在第2
步的調用方式不一樣,在返回Observablez以前可能會作一些轉換。
execute()
:經過調用queue()
來獲得一個Future對象,而後調用get()
方法來獲取Future中包含的值。queue()
:將Observable轉換成BlockingObservable
,在將BlockingObservable
轉換成一個Future。observe()
:訂閱返回的Observable,而且當即開始執行命令的邏輯,toObservable()
:返回一個沒有改變的Observable,你必須訂閱它,它纔可以開始執行命令的邏輯。下面的圖展現了HystrixCommand
和HystrixObservableCommand
如何與HystrixCircuitBroker
進行交互。
迴路器打開和關閉有以下幾種狀況:
HystrixCommandProperties.circuitBreakerRequestVolumeThreshold()
)HystrixCommandProperties.circuitBreakerErrorThresholdPercentage()
CLOSE
變換成OPEN
HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()
,下一個的請求會被經過(處於半打開狀態),若是該請求執行失敗,迴路器會在睡眠窗口期間返回OPEN
,若是請求成功,迴路器會被置爲關閉狀態,從新開啓1
步驟的邏輯。Hystrix採用艙壁模式來隔離相互之間的依賴關係,並限制對其中任何一個的併發訪問。
客戶端(第三方包、網絡調用等)會在單獨的線程執行,會與調用的該任務的線程進行隔離,以此來防止調用者調用依賴所消耗的時間過長而阻塞調用者的線程。
[Hystrix uses separate, per-dependency thread pools as a way of constraining any given dependency so latency on the underlying executions will saturate the available threads only in that pool]
您能夠在不使用線程池的狀況下防止出現故障,可是這要求客戶端必須可以作到快速失敗(網絡鏈接/讀取超時和重試配置),並始終保持良好的執行狀態。
Netflix,設計Hystrix,而且選擇使用線程和線程池來實現隔離機制,有如下幾個緣由:
使用線程池的好處
經過線程在本身的線程池中隔離的好處是:
簡而言之,由線程池提供的隔離功能可使客戶端庫和子系統性能特性的不斷變化和動態組合獲得優雅的處理,而不會形成中斷。
注意:雖然單獨的線程提供了隔離,但您的底層客戶端代碼也應該有超時和/或響應線程中斷,而不能讓Hystrix的線程池處於無休止的等待狀態。
線程池最主要的缺點就是增長了CPU的計算開銷,每一個命令都會在單獨的線程池上執行,這樣的執行方式會涉及到命令的排隊、調度和上下文切換。
Netflix在設計這個系統時,決定接受這個開銷的代價,來換取它所提供的好處,而且認爲這個開銷是足夠小的,不會有重大的成本或者是性能影響。
Hystrix在子線程執行construct()
方法和run()
方法時會計算延遲,以及計算父線程從端到端的執行總時間。因此,你能夠看到Hystrix開銷成本包括(線程、度量,日誌,斷路器等)。
Netflix API天天使用線程隔離的方式處理10億多的Hystrix Command任務,每一個API實例都有40多個線程池,每一個線程池都有5-20個線程(大多數設置爲10)
下圖顯示了一個HystrixCommand在單個API實例上每秒執行60個請求(每一個服務器每秒執行大約350個線程執行總數):
在中間位置(或者下線位置)不須要單獨的線程池。
在第90線上,單獨線程的成本爲3ms。
在第99線上,單獨的線程花費9ms。可是請注意,線程成本的開銷增長遠小於單獨線程(網絡請求)從2跳到28而執行時間從0跳到9的增長。
對於大多數Netflix用例來講,這樣的請求在90%以上的開銷被認爲是能夠接受的,這是爲了實現韌性的好處。
對於很是低延遲請求(例如那些主要觸發內存緩存的請求),開銷可能過高,在這種狀況下,可使用另外一種方法,如信號量,雖然它們不容許超時,提供絕大部分的有點,而不會產生開銷。然而,通常來講,開銷是比較小的,以致於Netflix一般更偏向於經過單獨的線程來做爲隔離實現。
您可使用請求合併器(HystrixCollapser是抽象父代)來提早發送HystrixCommand,經過該合併器您能夠將多個請求合併爲一個後端依賴項調用。
下面的圖展現了兩種狀況下的線程數和網絡鏈接數,第一張圖是不使用請求合併,第二張圖是使用請求合併(假定全部鏈接在短期窗口內是「併發的」,在這種狀況下是10ms)。
爲何使用請求合併
事情請求合併來減小執行併發HystrixCommand請求所須要的線程數和網絡鏈接數。請求合併以自動方式執行的,不須要代碼層面上進行批處理請求的編碼。
理想的合併方式是在全局應用程序級別來完成的,以便來自任何用戶的任何Tomcat線程的請求均可以一塊兒合併。
例如,若是將HystrixCommand配置爲支持任何用戶請求獲取影片評級的依賴項的批處理,那麼當同一個JVM中的任何用戶線程發出這樣的請求時,Hystrix會將該請求與其餘請求一塊兒合併添加到同一個JVM中的網絡調用。
請注意,合併器會將一個HystrixRequestContext對象傳遞給合併的網絡調用,爲了使其成爲一個有效選項,下游系統必須處理這種狀況。
若是將HystrixCommand配置爲僅處理單個用戶的批處理請求,則Hystrix僅僅會合並單個Tomcat線程的請求。
例如,若是一個用戶想要加載300個影片的標籤,Hystrix可以把這300次網絡調用合併成一次調用。
有時候,當你建立一個對象模型對消費的對象而言是具備邏輯意義的,這與對象的生產者的有效資源利用率不匹配。
例如,給你300個視頻對象,遍歷他們,而且調用他們的getSomeAttribute()
方法,可是若是簡單的調用,可能會致使300次網絡調用(可能很快會佔滿資源)。
有一些手動的方法能夠解決這個問題,好比在用戶調用getSomeAttribute()
方法以前,要求用戶聲明他們想要獲取哪些視頻對象的屬性,以便他們均可以被預取。
或者,您能夠分割對象模型,以便用戶必須從一個位置獲取視頻列表,而後從其餘位置請求該視頻列表的屬性。
這些方法能夠會使你的API和對象模型顯得笨拙,而且這種方式也不符合心理模式與使用模式(譯者:不太懂什麼意思)。因爲多個開發人員在代碼庫上工做,可能會致使低級的錯誤和低效率開發的問題。由於對一個用例的優化能夠經過執行另外一個用例和經過代碼的新路徑來打破。
經過將合併邏輯移到Hystrix層,無論你如何建立對象模型,調用順序是怎樣的,或者不一樣的開發人員是否知道是否完成了優化或者是否完成。
getSomeAttribute()方法能夠放在最適合的地方,並以任何適合使用模式的方式被調用,而且合併器會自動將批量調用放置到時間窗口。
####請求Cache
*
HystrixCommand和HystrixObservableCommand實現能夠定義一個緩存鍵,而後用這個緩存鍵以併發感知的方式在請求上下文中取消調用(不須要調用依賴便可以獲得結果,由於一樣的請求結果已經按照緩存鍵緩存起來了)。
如下是一個涉及HTTP請求生命週期的示例流程,以及在該請求中執行工做的兩個線程:
請求cache的好處有:
這在許多開發人員實現不一樣功能的大型代碼庫中尤爲有用。
例如,多個請求路徑都須要獲取用戶的Account對象,能夠像這樣請求:
Account account = new UserGetAccount(accountId).execute(); //or Observable<Account> accountObservable = new UserGetAccount(accountId).observe();
Hystrix RequestCache將只執行一次底層的run()方法,執行HystrixCommand的兩個線程都會收到相同的數據,儘管實例化了多個不一樣的實例。
每次執行該命令時,再也不會返回一個不一樣的值(或回退),而是將第一個響應緩存起來,後續相同的請求將會返回緩存的響應。
因爲請求緩存位於construct()或run()方法調用以前,Hystrix能夠在調用線程執行以前取消調用。
若是Hystrix沒有實現請求緩存功能,那麼每一個命令都須要在構造或者運行方法中實現,這將在一個線程排隊並執行以後進行。