導(dǎo)語(yǔ):網(wǎng)上資料(尤其中文文檔)對(duì)hystrix基礎(chǔ)功能的解釋比較籠統(tǒng)酱畅,看了往往一頭霧水。為此江场,本文將通過(guò)若干demo纺酸,加入對(duì)官網(wǎng)How-it-Works的理解和翻譯,力求更清晰解釋hystrix的基礎(chǔ)功能址否。所用demo均對(duì)官網(wǎng)How-To-Use進(jìn)行了二次修改餐蔬,見(jiàn)https://github.com/star2478/java-hystrix
Hystrix是Netflix開(kāi)源的一款容錯(cuò)系統(tǒng),能幫助使用者碼出具備強(qiáng)大的容錯(cuò)能力和魯棒性的程序佑附。如果某程序或class要使用Hystrix樊诺,只需簡(jiǎn)單繼承HystrixCommand/HystrixObservableCommand
并重寫(xiě)run()/construct()
,然后調(diào)用程序?qū)嵗薱lass并執(zhí)行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词爬,要重寫(xiě)Observable construct()
@Override
protected String run() {
return "Hello " + name;
}
}
/* 調(diào)用程序?qū)elloWorldHystrixCommand實(shí)例化,執(zhí)行execute()即觸發(fā)HelloWorldHystrixCommand.run()的執(zhí)行 */
String result = new HelloWorldHystrixCommand("HLX").execute();
System.out.println(result); // 打印出Hello HLX
pom.xml加上以下依賴权均。spring cloud也集成了hystrix顿膨,不過(guò)本文只介紹原生hystrix。
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>1.5.8</version>
</dependency>
本文重點(diǎn)介紹的是Hystrix各項(xiàng)基礎(chǔ)能力的用法及其效果螺句,不從零介紹hystrix虽惭,要了解基礎(chǔ)知識(shí)推薦官網(wǎng)wiki或民間blog
1、HystrixCommand vs HystrixObservableCommand
要想使用hystrix蛇尚,只需要繼承HystrixCommand
或HystrixObservableCommand
芽唇,簡(jiǎn)單用法見(jiàn)上面例子。兩者主要區(qū)別是:
前者的命令邏輯寫(xiě)在
run()
取劫;后者的命令邏輯寫(xiě)在construct()
前者的
run()
是由新創(chuàng)建的線程執(zhí)行匆笤;后者的construct()
是由調(diào)用程序線程執(zhí)行前者一個(gè)實(shí)例只能向調(diào)用程序發(fā)送(emit)單條數(shù)據(jù),比如上面例子中
run()
只能返回一個(gè)String結(jié)果谱邪;后者一個(gè)實(shí)例可以順序發(fā)送多條數(shù)據(jù)炮捧,比如demo中順序調(diào)用多個(gè)onNext()
,便實(shí)現(xiàn)了向調(diào)用程序發(fā)送多條數(shù)據(jù)惦银,甚至還能發(fā)送一個(gè)范圍的數(shù)據(jù)集
2咆课、4個(gè)命令執(zhí)行方法
execute()
、queue()
扯俱、observe()
书蚪、toObservable()
這4個(gè)方法用來(lái)觸發(fā)執(zhí)行run()/construct()
,一個(gè)實(shí)例只能執(zhí)行一次這4個(gè)方法迅栅,特別說(shuō)明的是HystrixObservableCommand
沒(méi)有execute()
和queue()
殊校。
4個(gè)方法的主要區(qū)別是:
execute()
:以同步堵塞方式執(zhí)行run()
。以demo為例读存,調(diào)用execute()
后为流,hystrix先創(chuàng)建一個(gè)新線程運(yùn)行run()
呕屎,接著調(diào)用程序要在execute()
調(diào)用處一直堵塞著,直到run()
運(yùn)行完成queue()
:以異步非堵塞方式執(zhí)行run()
敬察。以demo為例秀睛,一調(diào)用queue()
就直接返回一個(gè)Future對(duì)象,同時(shí)hystrix創(chuàng)建一個(gè)新線程運(yùn)行run()
静汤,調(diào)用程序通過(guò)Future.get()
拿到run()
的返回結(jié)果琅催,而Future.get()
是堵塞執(zhí)行的observe()
:事件注冊(cè)前執(zhí)行run()/construct()
居凶。以demo為例虫给,第一步是事件注冊(cè)前,先調(diào)用observe()
自動(dòng)觸發(fā)執(zhí)行run()/construct()
(如果繼承的是HystrixCommand
侠碧,hystrix將創(chuàng)建新線程非堵塞執(zhí)行run()
抹估;如果繼承的是HystrixObservableCommand
,將以調(diào)用程序線程堵塞執(zhí)行construct()
)弄兜,第二步是從observe()
返回后調(diào)用程序調(diào)用subscribe()
完成事件注冊(cè)药蜻,如果run()/construct()
執(zhí)行成功則觸發(fā)onNext()
和onCompleted()
,如果執(zhí)行異常則觸發(fā)onError()
toObservable()
:事件注冊(cè)后執(zhí)行run()/construct()
替饿。以demo為例语泽,第一步是事件注冊(cè)前,一調(diào)用toObservable()
就直接返回一個(gè)Observable<String>
對(duì)象视卢,第二步調(diào)用subscribe()
完成事件注冊(cè)后自動(dòng)觸發(fā)執(zhí)行run()/construct()
(如果繼承的是HystrixCommand
踱卵,hystrix將創(chuàng)建新線程非堵塞執(zhí)行run()
,調(diào)用程序不必等待run()
据过;如果繼承的是HystrixObservableCommand
惋砂,將以調(diào)用程序線程堵塞執(zhí)行construct()
,調(diào)用程序等待construct()
執(zhí)行完才能繼續(xù)往下走)绳锅,如果run()/construct()
執(zhí)行成功則觸發(fā)onNext()
和onCompleted()
西饵,如果執(zhí)行異常則觸發(fā)onError()
3、fallback(降級(jí))
使用fallback機(jī)制很簡(jiǎn)單鳞芙,繼承HystrixCommand
只需重寫(xiě)getFallback()
眷柔,繼承HystrixObservableCommand
只需重寫(xiě)resumeWithFallback()
,比如HelloWorldHystrixCommand
加上下面代碼片段:
@Override
protected String getFallback() {
return "fallback: " + name;
}
fallback實(shí)際流程是當(dāng)run()/construct()
被觸發(fā)執(zhí)行時(shí)或執(zhí)行中發(fā)生錯(cuò)誤時(shí)原朝,將轉(zhuǎn)向執(zhí)行getFallback()/resumeWithFallback()
驯嘱。結(jié)合下圖,4種錯(cuò)誤情況將觸發(fā)fallback:
非HystrixBadRequestException異常:以demo為例竿拆,當(dāng)拋出HystrixBadRequestException時(shí)宙拉,調(diào)用程序可以捕獲異常,沒(méi)有觸發(fā)
getFallback()
丙笋,而其他異常則會(huì)觸發(fā)getFallback()
谢澈,調(diào)用程序?qū)@得getFallback()
的返回run()/construct()
運(yùn)行超時(shí):以demo為例煌贴,使用無(wú)限while循環(huán)或sleep模擬超時(shí),觸發(fā)了getFallback()
熔斷器啟動(dòng):以demo為例锥忿,我們配置10s內(nèi)請(qǐng)求數(shù)大于3個(gè)時(shí)就啟動(dòng)熔斷器牛郑,請(qǐng)求錯(cuò)誤率大于80%時(shí)就熔斷,然后for循環(huán)發(fā)起請(qǐng)求敬鬓,當(dāng)請(qǐng)求符合熔斷條件時(shí)將觸發(fā)
getFallback()
淹朋。更多熔斷策略見(jiàn)下文線程池/信號(hào)量已滿:以demo為例,我們配置線程池?cái)?shù)目為3钉答,然后先用一個(gè)for循環(huán)執(zhí)行
queue()
础芍,觸發(fā)的run()
sleep 2s,然后再用第2個(gè)for循環(huán)執(zhí)行execute()
数尿,發(fā)現(xiàn)所有execute()
都觸發(fā)了fallback仑性,這是因?yàn)榈?個(gè)for的線程還在sleep,占用著線程池所有線程右蹦,導(dǎo)致第2個(gè)for的所有命令都無(wú)法獲取到線程
調(diào)用程序可以通過(guò)
isResponseFromFallback()
查詢結(jié)果是由run()/construct()
還是getFallback()/resumeWithFallback()
返回的
4诊杆、隔離策略
hystrix提供了兩種隔離策略:線程池隔離和信號(hào)量隔離。hystrix默認(rèn)采用線程池隔離何陆。
線程池隔離:不同服務(wù)通過(guò)使用不同線程池晨汹,彼此間將不受影響,達(dá)到隔離效果贷盲。以demo為例淘这,我們通過(guò)andThreadPoolKey配置使用命名為
ThreadPoolTest
的線程池,實(shí)現(xiàn)與其他命名的線程池天然隔離晃洒,如果不配置andThreadPoolKey則使用withGroupKey配置來(lái)命名線程池信號(hào)量隔離:線程隔離會(huì)帶來(lái)線程開(kāi)銷(xiāo)慨灭,有些場(chǎng)景(比如無(wú)網(wǎng)絡(luò)請(qǐng)求場(chǎng)景)可能會(huì)因?yàn)橛瞄_(kāi)銷(xiāo)換隔離得不償失,為此hystrix提供了信號(hào)量隔離球及,當(dāng)服務(wù)的并發(fā)數(shù)大于信號(hào)量閾值時(shí)將進(jìn)入fallback氧骤。以demo為例,通過(guò)
withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)
配置為信號(hào)量隔離吃引,通過(guò)withExecutionIsolationSemaphoreMaxConcurrentRequests
配置執(zhí)行并發(fā)數(shù)不能大于3筹陵,由于信號(hào)量隔離下無(wú)論調(diào)用哪種命令執(zhí)行方法,hystrix都不會(huì)創(chuàng)建新線程執(zhí)行run()/construct()
镊尺,所以調(diào)用程序需要自己創(chuàng)建多個(gè)線程來(lái)模擬并發(fā)調(diào)用execute()
朦佩,最后看到一旦并發(fā)線程>3,后續(xù)請(qǐng)求都進(jìn)入fallback
5庐氮、熔斷機(jī)制
熔斷機(jī)制相當(dāng)于電路的跳閘功能语稠,舉個(gè)栗子,我們可以配置熔斷策略為當(dāng)請(qǐng)求錯(cuò)誤比例在10s內(nèi)>50%時(shí),該服務(wù)將進(jìn)入熔斷狀態(tài)仙畦,后續(xù)請(qǐng)求都會(huì)進(jìn)入fallback输涕。
以demo為例,我們通過(guò)withCircuitBreakerRequestVolumeThreshold
配置10s內(nèi)請(qǐng)求數(shù)超過(guò)3個(gè)時(shí)熔斷器開(kāi)始生效慨畸,通過(guò)withCircuitBreakerErrorThresholdPercentage
配置錯(cuò)誤比例>80%時(shí)開(kāi)始熔斷莱坎,然后for循環(huán)執(zhí)行execute()
觸發(fā)run()
,在run()
里寸士,如果name
是小于10的偶數(shù)則正常返回檐什,否則超時(shí),通過(guò)多次循環(huán)后弱卡,超時(shí)請(qǐng)求占所有請(qǐng)求的比例將大于80%乃正,就會(huì)看到后續(xù)請(qǐng)求都不進(jìn)入run()
而是進(jìn)入getFallback()
,因?yàn)椴辉俅蛴?code>"running run():" + name了谐宙。
除此之外烫葬,hystrix還支持多長(zhǎng)時(shí)間從熔斷狀態(tài)自動(dòng)恢復(fù)等功能界弧,見(jiàn)下文附錄凡蜻。
6、結(jié)果cache
hystrix支持將一個(gè)請(qǐng)求結(jié)果緩存起來(lái)垢箕,下一個(gè)具有相同key的請(qǐng)求將直接從緩存中取出結(jié)果划栓,減少請(qǐng)求開(kāi)銷(xiāo)。要使用hystrix cache功能条获,第一個(gè)要求是重寫(xiě)getCacheKey()
忠荞,用來(lái)構(gòu)造cache key;第二個(gè)要求是構(gòu)建context帅掘,如果請(qǐng)求B要用到請(qǐng)求A的結(jié)果緩存委煤,A和B必須同處一個(gè)context。通過(guò)HystrixRequestContext.initializeContext()
和context.shutdown()
可以構(gòu)建一個(gè)context修档,這兩條語(yǔ)句間的所有請(qǐng)求都處于同一個(gè)context碧绞。
以demo的testWithCacheHits()
為例,command2a吱窝、command2b讥邻、command2c同處一個(gè)context,前兩者的cache key都是2HLX
(見(jiàn)getCacheKey()
)院峡,所以command2a執(zhí)行完后把結(jié)果緩存兴使,command2b執(zhí)行時(shí)就不走run()
而是直接從緩存中取結(jié)果了,而command2c的cache key是2HLX1
照激,無(wú)法從緩存中取結(jié)果发魄。此外,通過(guò)isResponseFromCache()
可檢查返回結(jié)果是否來(lái)自緩存俩垃。
7励幼、合并請(qǐng)求collapsing
hystrix支持N個(gè)請(qǐng)求自動(dòng)合并為一個(gè)請(qǐng)求欢策,這個(gè)功能在有網(wǎng)絡(luò)交互的場(chǎng)景下尤其有用,比如每個(gè)請(qǐng)求都要網(wǎng)絡(luò)訪問(wèn)遠(yuǎn)程資源赏淌,如果把請(qǐng)求合并為一個(gè)踩寇,將使多次網(wǎng)絡(luò)交互變成一次,極大節(jié)省開(kāi)銷(xiāo)六水。重要一點(diǎn)俺孙,兩個(gè)請(qǐng)求能自動(dòng)合并的前提是兩者足夠“近”,即兩者啟動(dòng)執(zhí)行的間隔時(shí)長(zhǎng)要足夠小掷贾,默認(rèn)為10ms睛榄,即超過(guò)10ms將不自動(dòng)合并。
以demo為例想帅,我們連續(xù)發(fā)起多個(gè)queue請(qǐng)求场靴,依次返回f1~f6共6個(gè)Future對(duì)象,根據(jù)打印結(jié)果可知f1~f5同處一個(gè)線程港准,說(shuō)明這5個(gè)請(qǐng)求被合并了旨剥,而f6由另一個(gè)線程執(zhí)行,這是因?yàn)?em>f5和f6中間隔了一個(gè)sleep浅缸,超過(guò)了合并要求的最大間隔時(shí)長(zhǎng)轨帜。
附錄:各種策略配置
根據(jù)http://hot66hot.iteye.com/blog/2155036 整理而得。
- HystrixCommandProperties
/* --------------統(tǒng)計(jì)相關(guān)------------------*/
// 統(tǒng)計(jì)滾動(dòng)的時(shí)間窗口,默認(rèn):5000毫秒(取自circuitBreakerSleepWindowInMilliseconds)
private final HystrixProperty metricsRollingStatisticalWindowInMilliseconds;
// 統(tǒng)計(jì)窗口的Buckets的數(shù)量,默認(rèn):10個(gè),每秒一個(gè)Buckets統(tǒng)計(jì)
private final HystrixProperty metricsRollingStatisticalWindowBuckets; // number of buckets in the statisticalWindow
// 是否開(kāi)啟監(jiān)控統(tǒng)計(jì)功能,默認(rèn):true
private final HystrixProperty metricsRollingPercentileEnabled;
/* --------------熔斷器相關(guān)------------------*/
// 熔斷器在整個(gè)統(tǒng)計(jì)時(shí)間內(nèi)是否開(kāi)啟的閥值衩椒,默認(rèn)20蚌父。也就是在metricsRollingStatisticalWindowInMilliseconds(默認(rèn)10s)內(nèi)至少請(qǐng)求20次,熔斷器才發(fā)揮起作用
private final HystrixProperty circuitBreakerRequestVolumeThreshold;
// 熔斷時(shí)間窗口毛萌,默認(rèn):5秒.熔斷器中斷請(qǐng)求5秒后會(huì)進(jìn)入半打開(kāi)狀態(tài),放下一個(gè)請(qǐng)求進(jìn)來(lái)重試苟弛,如果該請(qǐng)求成功就關(guān)閉熔斷器,否則繼續(xù)等待一個(gè)熔斷時(shí)間窗口
private final HystrixProperty circuitBreakerSleepWindowInMilliseconds;
//是否啟用熔斷器,默認(rèn)true. 啟動(dòng)
private final HystrixProperty circuitBreakerEnabled;
//默認(rèn):50%阁将。當(dāng)出錯(cuò)率超過(guò)50%后熔斷器啟動(dòng)
private final HystrixProperty circuitBreakerErrorThresholdPercentage;
//是否強(qiáng)制開(kāi)啟熔斷器阻斷所有請(qǐng)求,默認(rèn):false,不開(kāi)啟膏秫。置為true時(shí),所有請(qǐng)求都將被拒絕冀痕,直接到fallback
private final HystrixProperty circuitBreakerForceOpen;
//是否允許熔斷器忽略錯(cuò)誤,默認(rèn)false, 不開(kāi)啟
private final HystrixProperty circuitBreakerForceClosed;
/* --------------信號(hào)量相關(guān)------------------*/
//使用信號(hào)量隔離時(shí)荔睹,命令調(diào)用最大的并發(fā)數(shù),默認(rèn):10
private final HystrixProperty executionIsolationSemaphoreMaxConcurrentRequests;
//使用信號(hào)量隔離時(shí),命令fallback(降級(jí))調(diào)用最大的并發(fā)數(shù),默認(rèn):10
private final HystrixProperty fallbackIsolationSemaphoreMaxConcurrentRequests;
/* --------------其他------------------*/
//使用命令調(diào)用隔離方式,默認(rèn):采用線程隔離,ExecutionIsolationStrategy.THREAD
private final HystrixProperty executionIsolationStrategy;
//使用線程隔離時(shí)言蛇,調(diào)用超時(shí)時(shí)間僻他,默認(rèn):1秒
private final HystrixProperty executionIsolationThreadTimeoutInMilliseconds;
//線程池的key,用于決定命令在哪個(gè)線程池執(zhí)行
private final HystrixProperty executionIsolationThreadPoolKeyOverride;
//是否開(kāi)啟fallback降級(jí)策略 默認(rèn):true
private final HystrixProperty fallbackEnabled;
// 使用線程隔離時(shí),是否對(duì)命令執(zhí)行超時(shí)的線程調(diào)用中斷(Thread.interrupt())操作.默認(rèn):true
private final HystrixProperty executionIsolationThreadInterruptOnTimeout;
// 是否開(kāi)啟請(qǐng)求日志,默認(rèn):true
private final HystrixProperty requestLogEnabled;
//是否開(kāi)啟請(qǐng)求緩存,默認(rèn):true
private final HystrixProperty requestCacheEnabled; // Whether request caching is enabled.
- HystrixCollapserProperties
//請(qǐng)求合并是允許的最大請(qǐng)求數(shù),默認(rèn): Integer.MAX_VALUE
private final HystrixProperty maxRequestsInBatch;
//批處理過(guò)程中每個(gè)命令延遲的時(shí)間,默認(rèn):10毫秒
private final HystrixProperty timerDelayInMilliseconds;
//批處理過(guò)程中是否開(kāi)啟請(qǐng)求緩存,默認(rèn):開(kāi)啟
private final HystrixProperty requestCacheEnabled;
- HystrixThreadPoolProperties
/* 配置線程池大小,默認(rèn)值10個(gè) */
private final HystrixProperty corePoolSize;
/* 配置線程值等待隊(duì)列長(zhǎng)度,默認(rèn)值:-1 建議值:-1表示不等待直接拒絕,測(cè)試表明線程池使用直接決絕策略+ 合適大小的非回縮線程池效率最高.所以不建議修改此值腊尚。 當(dāng)使用非回縮線程池時(shí)吨拗,queueSizeRejectionThreshold,keepAliveTimeMinutes 參數(shù)無(wú)效 */
private final HystrixProperty maxQueueSize;
參考文獻(xiàn)
https://github.com/Netflix/Hystrix
https://github.com/Netflix/Hystrix/wiki/How-To-Use
http://hot66hot.iteye.com/blog/2155036