https://www.jianshu.com/p/b9af028efebb
導語:網上資料(尤其中文文檔)對hystrix基礎功能的解釋比較籠統,看了往往一頭霧水。爲此,本文將通過若干demo,加入對官網How-it-Works的理解和翻譯,力求更清晰解釋hystrix的基礎功能。所用demo均對官網How-To-Use進行了二次修改,見https://github.com/star2478/java-hystrix
Hystrix是Netflix開源的一款容錯系統,能幫助使用者碼出具備強大的容錯能力和魯棒性的程序。如果某程序或class要使用Hystrix,只需簡單繼承HystrixCommand/HystrixObservableCommand
並重寫run()/construct()
,然後調用程序實例化此class並執行execute()/queue()/observe()/toObservable()
。
// HelloWorldHystrixCommand要使用Hystrix功能
public class HelloWorldHystrixCommand extends HystrixCommand {
private final String name;
public HelloWorldHystrixCommand(String name) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name;
}
// 如果繼承的是HystrixObservableCommand,要重寫Observable construct()
@Override
protected String run() {
return "Hello " + name;
}
}
/* 調用程序對HelloWorldHystrixCommand實例化,執行execute()即觸發HelloWorldHystrixCommand.run()的執行 */
String result = new HelloWorldHystrixCommand("HLX").execute();
System.out.println(result); // 打印出Hello HLX
pom.xml加上以下依賴。spring cloud也集成了hystrix,不過本文只介紹原生hystrix。
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>1.5.8</version>
</dependency>
本文重點介紹的是Hystrix各項基礎能力的用法及其效果,不從零介紹hystrix,要了解基礎知識推薦官網wiki或民間blog
1、HystrixCommand vs HystrixObservableCommand
要想使用hystrix,只需要繼承HystrixCommand
或HystrixObservableCommand
,簡單用法見上面例子。兩者主要區別是:
-
前者的命令邏輯寫在
run()
;後者的命令邏輯寫在construct()
-
前者的
run()
是由新創建的線程執行;後者的construct()
是由調用程序線程執行 -
前者一個實例只能向調用程序發送(emit)單條數據,比如上面例子中
run()
只能返回一個String結果;後者一個實例可以順序發送多條數據,比如demo中順序調用多個onNext()
,便實現了向調用程序發送多條數據,甚至還能發送一個範圍的數據集
2、4個命令執行方法
execute()
、queue()
、observe()
、toObservable()
這4個方法用來觸發執行run()/construct()
,一個實例只能執行一次這4個方法,特別說明的是HystrixObservableCommand
沒有execute()
和queue()
。
4個方法的主要區別是:
-
execute()
:以同步堵塞方式執行run()
。以demo爲例,調用execute()
後,hystrix先創建一個新線程運行run()
,接着調用程序要在execute()
調用處一直堵塞着,直到run()
運行完成 -
queue()
:以異步非堵塞方式執行run()
。以demo爲例,一調用queue()
就直接返回一個Future對象,同時hystrix創建一個新線程運行run()
,調用程序通過Future.get()
拿到run()
的返回結果,而Future.get()
是堵塞執行的 -
observe()
:事件註冊前執行run()/construct()
。以demo爲例,第一步是事件註冊前,先調用observe()
自動觸發執行run()/construct()
(如果繼承的是HystrixCommand
,hystrix將創建新線程非堵塞執行run()
;如果繼承的是HystrixObservableCommand
,將以調用程序線程堵塞執行construct()
),第二步是從observe()
返回後調用程序調用subscribe()
完成事件註冊,如果run()/construct()
執行成功則觸發onNext()
和onCompleted()
,如果執行異常則觸發onError()
-
toObservable()
:事件註冊後執行run()/construct()
。以demo爲例,第一步是事件註冊前,一調用toObservable()
就直接返回一個Observable<String>
對象,第二步調用subscribe()
完成事件註冊後自動觸發執行run()/construct()
(如果繼承的是HystrixCommand
,hystrix將創建新線程非堵塞執行run()
,調用程序不必等待run()
;如果繼承的是HystrixObservableCommand
,將以調用程序線程堵塞執行construct()
,調用程序等待construct()
執行完才能繼續往下走),如果run()/construct()
執行成功則觸發onNext()
和onCompleted()
,如果執行異常則觸發onError()
3、fallback(降級)
使用fallback機制很簡單,繼承HystrixCommand
只需重寫getFallback()
,繼承HystrixObservableCommand
只需重寫resumeWithFallback()
,比如HelloWorldHystrixCommand
加上下面代碼片段:
@Override
protected String getFallback() {
return "fallback: " + name;
}
fallback實際流程是當run()/construct()
被觸發執行時或執行中發生錯誤時,將轉向執行getFallback()/resumeWithFallback()
。結合下圖,4種錯誤情況將觸發fallback:
-
非HystrixBadRequestException異常:以demo爲例,當拋出HystrixBadRequestException時,調用程序可以捕獲異常,沒有觸發
getFallback()
,而其他異常則會觸發getFallback()
,調用程序將獲得getFallback()
的返回 -
run()/construct()
運行超時:以demo爲例,使用無限while循環或sleep模擬超時,觸發了getFallback()
-
熔斷器啓動:以demo爲例,我們配置10s內請求數大於3個時就啓動熔斷器,請求錯誤率大於80%時就熔斷,然後for循環發起請求,當請求符合熔斷條件時將觸發
getFallback()
。更多熔斷策略見下文 -
線程池/信號量已滿:以demo爲例,我們配置線程池數目爲3,然後先用一個for循環執行
queue()
,觸發的run()
sleep 2s,然後再用第2個for循環執行execute()
,發現所有execute()
都觸發了fallback,這是因爲第1個for的線程還在sleep,佔用着線程池所有線程,導致第2個for的所有命令都無法獲取到線程
來自hystrix github wiki
調用程序可以通過
isResponseFromFallback()
查詢結果是由run()/construct()
還是getFallback()/resumeWithFallback()
返回的
4、隔離策略
hystrix提供了兩種隔離策略:線程池隔離和信號量隔離。hystrix默認採用線程池隔離。
-
線程池隔離:不同服務通過使用不同線程池,彼此間將不受影響,達到隔離效果。以demo爲例,我們通過andThreadPoolKey配置使用命名爲
ThreadPoolTest
的線程池,實現與其他命名的線程池天然隔離,如果不配置andThreadPoolKey則使用withGroupKey配置來命名線程池 -
信號量隔離:線程隔離會帶來線程開銷,有些場景(比如無網絡請求場景)可能會因爲用開銷換隔離得不償失,爲此hystrix提供了信號量隔離,當服務的併發數大於信號量閾值時將進入fallback。以demo爲例,通過
withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)
配置爲信號量隔離,通過withExecutionIsolationSemaphoreMaxConcurrentRequests
配置執行併發數不能大於3,由於信號量隔離下無論調用哪種命令執行方法,hystrix都不會創建新線程執行run()/construct()
,所以調用程序需要自己創建多個線程來模擬併發調用execute()
,最後看到一旦併發線程>3,後續請求都進入fallback
5、熔斷機制
熔斷機制相當於電路的跳閘功能,舉個栗子,我們可以配置熔斷策略爲當請求錯誤比例在10s內>50%時,該服務將進入熔斷狀態,後續請求都會進入fallback。
以demo爲例,我們通過withCircuitBreakerRequestVolumeThreshold
配置10s內請求數超過3個時熔斷器開始生效,通過withCircuitBreakerErrorThresholdPercentage
配置錯誤比例>80%時開始熔斷,然後for循環執行execute()
觸發run()
,在run()
裏,如果name
是小於10的偶數則正常返回,否則超時,通過多次循環後,超時請求佔所有請求的比例將大於80%,就會看到後續請求都不進入run()
而是進入getFallback()
,因爲不再打印"running run():" + name
了。
除此之外,hystrix還支持多長時間從熔斷狀態自動恢復等功能,見下文附錄。
6、結果cache
hystrix支持將一個請求結果緩存起來,下一個具有相同key的請求將直接從緩存中取出結果,減少請求開銷。要使用hystrix cache功能,第一個要求是重寫getCacheKey()
,用來構造cache key;第二個要求是構建context,如果請求B要用到請求A的結果緩存,A和B必須同處一個context。通過HystrixRequestContext.initializeContext()
和context.shutdown()
可以構建一個context,這兩條語句間的所有請求都處於同一個context。
以demo的testWithCacheHits()
爲例,command2a、command2b、command2c同處一個context,前兩者的cache key都是2HLX
(見getCacheKey()
),所以command2a執行完後把結果緩存,command2b執行時就不走run()
而是直接從緩存中取結果了,而command2c的cache key是2HLX1
,無法從緩存中取結果。此外,通過isResponseFromCache()
可檢查返回結果是否來自緩存。
7、合併請求collapsing
hystrix支持N個請求自動合併爲一個請求,這個功能在有網絡交互的場景下尤其有用,比如每個請求都要網絡訪問遠程資源,如果把請求合併爲一個,將使多次網絡交互變成一次,極大節省開銷。重要一點,兩個請求能自動合併的前提是兩者足夠“近”,即兩者啓動執行的間隔時長要足夠小,默認爲10ms,即超過10ms將不自動合併。
以demo爲例,我們連續發起多個queue請求,依次返回f1~f6共6個Future對象,根據打印結果可知f1~f5同處一個線程,說明這5個請求被合併了,而f6由另一個線程執行,這是因爲f5和f6中間隔了一個sleep,超過了合併要求的最大間隔時長。
附錄:各種策略配置
根據http://hot66hot.iteye.com/blog/2155036 整理而得。
- HystrixCommandProperties
/* --------------統計相關------------------*/
// 統計滾動的時間窗口,默認:5000毫秒(取自circuitBreakerSleepWindowInMilliseconds)
private final HystrixProperty metricsRollingStatisticalWindowInMilliseconds;
// 統計窗口的Buckets的數量,默認:10個,每秒一個Buckets統計
private final HystrixProperty metricsRollingStatisticalWindowBuckets; // number of buckets in the statisticalWindow
// 是否開啓監控統計功能,默認:true
private final HystrixProperty metricsRollingPercentileEnabled;
/* --------------熔斷器相關------------------*/
// 熔斷器在整個統計時間內是否開啓的閥值,默認20。也就是在metricsRollingStatisticalWindowInMilliseconds(默認10s)內至少請求20次,熔斷器才發揮起作用
private final HystrixProperty circuitBreakerRequestVolumeThreshold;
// 熔斷時間窗口,默認:5秒.熔斷器中斷請求5秒後會進入半打開狀態,放下一個請求進來重試,如果該請求成功就關閉熔斷器,否則繼續等待一個熔斷時間窗口
private final HystrixProperty circuitBreakerSleepWindowInMilliseconds;
//是否啓用熔斷器,默認true. 啓動
private final HystrixProperty circuitBreakerEnabled;
//默認:50%。當出錯率超過50%後熔斷器啓動
private final HystrixProperty circuitBreakerErrorThresholdPercentage;
//是否強制開啓熔斷器阻斷所有請求,默認:false,不開啓。置爲true時,所有請求都將被拒絕,直接到fallback
private final HystrixProperty circuitBreakerForceOpen;
//是否允許熔斷器忽略錯誤,默認false, 不開啓
private final HystrixProperty circuitBreakerForceClosed;
/* --------------信號量相關------------------*/
//使用信號量隔離時,命令調用最大的併發數,默認:10
private final HystrixProperty executionIsolationSemaphoreMaxConcurrentRequests;
//使用信號量隔離時,命令fallback(降級)調用最大的併發數,默認:10
private final HystrixProperty fallbackIsolationSemaphoreMaxConcurrentRequests;
/* --------------其他------------------*/
//使用命令調用隔離方式,默認:採用線程隔離,ExecutionIsolationStrategy.THREAD
private final HystrixProperty executionIsolationStrategy;
//使用線程隔離時,調用超時時間,默認:1秒
private final HystrixProperty executionIsolationThreadTimeoutInMilliseconds;
//線程池的key,用於決定命令在哪個線程池執行
private final HystrixProperty executionIsolationThreadPoolKeyOverride;
//是否開啓fallback降級策略 默認:true
private final HystrixProperty fallbackEnabled;
// 使用線程隔離時,是否對命令執行超時的線程調用中斷(Thread.interrupt())操作.默認:true
private final HystrixProperty executionIsolationThreadInterruptOnTimeout;
// 是否開啓請求日誌,默認:true
private final HystrixProperty requestLogEnabled;
//是否開啓請求緩存,默認:true
private final HystrixProperty requestCacheEnabled; // Whether request caching is enabled.
- HystrixCollapserProperties
//請求合併是允許的最大請求數,默認: Integer.MAX_VALUE
private final HystrixProperty maxRequestsInBatch;
//批處理過程中每個命令延遲的時間,默認:10毫秒
private final HystrixProperty timerDelayInMilliseconds;
//批處理過程中是否開啓請求緩存,默認:開啓
private final HystrixProperty requestCacheEnabled;
- HystrixThreadPoolProperties
/* 配置線程池大小,默認值10個 */
private final HystrixProperty corePoolSize;
/* 配置線程值等待隊列長度,默認值:-1 建議值:-1表示不等待直接拒絕,測試表明線程池使用直接決絕策略+ 合適大小的非回縮線程池效率最高.所以不建議修改此值。 當使用非回縮線程池時,queueSizeRejectionThreshold,keepAliveTimeMinutes 參數無效 */
private final HystrixProperty maxQueueSize;
參考文獻
https://github.com/Netflix/Hystrix
https://github.com/Netflix/Hystrix/wiki/How-To-Use
http://hot66hot.iteye.com/blog/2155036
作者:star24
鏈接:https://www.jianshu.com/p/b9af028efebb
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。