摘要:使用線程池的好處通過線程在自己的線程池中隔離的好處是該應用程序完全可以不受失控的客戶端庫的威脅。簡而言之,由線程池提供的隔離功能可以使客戶端庫和子系統性能特性的不斷變化和動態組合得到優雅的處理,而不會造成中斷。
?
工作流程圖下面的流程圖展示了當使用Hystrix的依賴請求,Hystrix是如何工作的。
? 下面將更詳細的解析每一個步驟都發生哪些動作:
構建一個HystrixCommand或者HystrixObservableCommand對象。
第一步就是構建一個HystrixCommand或者HystrixObservableCommand對象,該對象將代表你的一個依賴請求,向構造函數中傳入請求依賴所需要的參數。
如果構建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(); FuturefValue = command.queue(); Observable ohValue = command.observe(); //hot observable Observable 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對象。
計算回路指標[Circuit Health]
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,并且選擇使用線程和線程池來實現隔離機制,有以下幾個原因:
很多應用會調用多個不同的后端服務作為依賴。
每個服務會提供自己的客戶端庫包。
每個客戶端的庫包都會不斷的處于變更狀態。
[Client library logic can change to add new network calls]
每個客戶端庫包都可能包含重試、數據解析、緩存等等其他邏輯。
對用戶來說,客戶端庫往往是“黑盒”的,對于實現細節、網絡訪問模式。默認配置等都是不透明的。
[In several real-world production outages the determination was “oh, something changed and properties should be adjusted” or “the client library changed its behavior.]
即使客戶端本身沒有改變,服務本身也可能發生變化,這些因素都會影響到服務的性能,從而導致客戶端配置失效。
傳遞依賴可以引入其他客戶端庫,這些客戶端庫不是預期的,也許沒有正確配置。
大部分的網絡訪問是同步執行的。
客戶端代碼中也可能出現失敗和延遲,而不僅僅是在網絡調用中。
使用線程池的好處
通過線程在自己的線程池中隔離的好處是:
該應用程序完全可以不受失控的客戶端庫的威脅。即使某一個依賴的線程池已滿也不會影響其他依賴的調用。
應用程序可以低風險的接受新的客戶端庫的數據,如果發生問題,它會與出問題的客戶端庫所隔離,不會影響其他依賴的任何內容。
當失敗的客戶端服務恢復時,線程池將會被清除,應用程序也會恢復,而不至于使得我們整個Tomcat容器出現故障。
如果一個客戶端庫的配置錯誤,線程池可以很快的感知這一錯誤(通過增加錯誤比例,延遲,超時,拒絕等),并可以在不影響應用程序的功能情況下來處理這些問題(可以通過動態配置來進行實時的改變)。
如果一個客戶端服務的性能變差,可以通過改變線程池的指標(錯誤、延遲、超時、拒絕)來進行屬性的調整,并且這些調整可以不影響其他的客戶端請求。
除了隔離的優勢之外,擁有專用的線程池可以提供內置的請求任務的并發性,可以在同步客戶端上構建異步門面。
簡而言之,由線程池提供的隔離功能可以使客戶端庫和子系統性能特性的不斷變化和動態組合得到優雅的處理,而不會造成中斷。
注意:雖然多帶帶的線程提供了隔離,但您的底層客戶端代碼也應該有超時和/或響應線程中斷,而不能讓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線程)
理想的合并方式是在全局應用程序級別來完成的,以便來自任何用戶的任何Tomcat線程的請求都可以一起合并。
例如,如果將HystrixCommand配置為支持任何用戶請求獲取影片評級的依賴項的批處理,那么當同一個JVM中的任何用戶線程發出這樣的請求時,Hystrix會將該請求與其他請求一起合并添加到同一個JVM中的網絡調用。
請注意,合并器會將一個HystrixRequestContext對象傳遞給合并的網絡調用,為了使其成為一個有效選項,下游系統必須處理這種情況。
用戶請求上下文(單個tomcat線程)
如果將HystrixCommand配置為僅處理單個用戶的批處理請求,則Hystrix僅僅會合并單個Tomcat線程的請求。
例如,如果一個用戶想要加載300個影片的標簽,Hystrix能夠把這300次網絡調用合并成一次調用。
對象建模和代碼的復雜性
有時候,當你創建一個對象模型對消費的對象而言是具有邏輯意義的,這與對象的生產者的有效資源利用率不匹配。
例如,給你300個視頻對象,遍歷他們,并且調用他們的getSomeAttribute()方法,但是如果簡單的調用,可能會導致300次網絡調用(可能很快會占滿資源)。
有一些手動的方法可以解決這個問題,比如在用戶調用getSomeAttribute()方法之前,要求用戶聲明他們想要獲取哪些視頻對象的屬性,以便他們都可以被預取。
或者,您可以分割對象模型,以便用戶必須從一個位置獲取視頻列表,然后從其他位置請求該視頻列表的屬性。
這些方法可以會使你的API和對象模型顯得笨拙,并且這種方式也不符合心理模式與使用模式(譯者:不太懂什么意思)。由于多個開發人員在代碼庫上工作,可能會導致低級的錯誤和低效率開發的問題。因為對一個用例的優化可以通過執行另一個用例和通過代碼的新路徑來打破。
通過將合并邏輯移到Hystrix層,不管你如何創建對象模型,調用順序是怎樣的,或者不同的開發人員是否知道是否完成了優化或者是否完成。
getSomeAttribute()方法可以放在最適合的地方,并以任何適合使用模式的方式被調用,并且合并器會自動將批量調用放置到時間窗口。
####請求Cache
*
HystrixCommand和HystrixObservableCommand實現可以定義一個緩存鍵,然后用這個緩存鍵以并發感知的方式在請求上下文中取消調用(不需要調用依賴即可以得到結果,因為同樣的請求結果已經按照緩存鍵緩存起來了)。
以下是一個涉及HTTP請求生命周期的示例流程,以及在該請求中執行工作的兩個線程:
請求cache的好處有:
不同的代碼路徑可以執行Hystrix命令,而不用擔心重復的工作。
這在許多開發人員實現不同功能的大型代碼庫中尤其有用。
例如,多個請求路徑都需要獲取用戶的Account對象,可以像這樣請求:
Account account = new UserGetAccount(accountId).execute(); //or ObservableaccountObservable = new UserGetAccount(accountId).observe();
Hystrix RequestCache將只執行一次底層的run()方法,執行HystrixCommand的兩個線程都會收到相同的數據,盡管實例化了多個不同的實例。
整個請求的數據檢索是一致的。
每次執行該命令時,不再會返回一個不同的值(或回退),而是將第一個響應緩存起來,后續相同的請求將會返回緩存的響應。
消除重復的線程執行。
由于請求緩存位于construct()或run()方法調用之前,Hystrix可以在調用線程執行之前取消調用。
如果Hystrix沒有實現請求緩存功能,那么每個命令都需要在構造或者運行方法中實現,這將在一個線程排隊并執行之后進行。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/70801.html
摘要:不過大多數講解還停留在對功能使用的層面,其底層的很多原理,很多人可能并不知曉。每個線程池里的線程就僅僅用于請求那個服務。 歡迎關注微信公眾號:石杉的架構筆記(id:shishan100) 每日更新!精品技術文章準時送上! 目錄 一、業務場景介紹 二、Spring Cloud核心組件:Eureka 三、Spring Cloud核心組件:Feign 四、Spring Cloud核心組件:R...
摘要:不過大多數講解還停留在對功能使用的層面,其底層的很多原理,很多人可能并不知曉。每個線程池里的線程就僅僅用于請求那個服務。 歡迎關注微信公眾號:石杉的架構筆記(id:shishan100) 每日更新!精品技術文章準時送上! 目錄 一、業務場景介紹 二、Spring Cloud核心組件:Eureka 三、Spring Cloud核心組件:Feign 四、Spring Cloud核心組件:R...
摘要:集群系統中的單個計算機通常稱為節點,通常通過局域網連接,但也有其它的可能連接方式。這樣就高興了,可以專心寫自己的,前端就專門交由小周負責了。于是,小周和就變成了協作開發。都是為了項目正常運行以及迭代。 一、前言 只有光頭才能變強 認識我的朋友可能都知道我這陣子去實習啦,去的公司說是用SpringCloud(但我覺得使用的力度并不大啊~~)... 所以,這篇主要來講講SpringClou...
摘要:集群系統中的單個計算機通常稱為節點,通常通過局域網連接,但也有其它的可能連接方式。這樣就高興了,可以專心寫自己的,前端就專門交由小周負責了。于是,小周和就變成了協作開發。都是為了項目正常運行以及迭代。 一、前言 只有光頭才能變強 認識我的朋友可能都知道我這陣子去實習啦,去的公司說是用SpringCloud(但我覺得使用的力度并不大啊~~)... 所以,這篇主要來講講SpringClou...
閱讀 2672·2019-08-30 15:55
閱讀 1804·2019-08-30 15:53
閱讀 2656·2019-08-29 18:38
閱讀 928·2019-08-26 13:49
閱讀 502·2019-08-23 15:42
閱讀 3113·2019-08-22 16:33
閱讀 1003·2019-08-21 17:59
閱讀 1082·2019-08-21 17:11