導(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加上以下依賴(lài)。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)建的線(xiàn)程執(zhí)行;后者的construct()是由調(diào)用程序線(xiàn)程執(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è)新線(xiàn)程運(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è)新線(xiàn)程運(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)建新線(xiàn)程非堵塞執(zhí)行run();如果繼承的是HystrixObservableCommand,將以調(diào)用程序線(xiàn)程堵塞執(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)建新線(xiàn)程非堵塞執(zhí)行run(),調(diào)用程序不必等待run();如果繼承的是HystrixObservableCommand,將以調(diào)用程序線(xiàn)程堵塞執(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)下文線(xiàn)程池/信號(hào)量已滿(mǎn):以demo為例,我們配置線(xiàn)程池?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的線(xiàn)程還在sleep,占用著線(xiàn)程池所有線(xiàn)程,導(dǎo)致第2個(gè)for的所有命令都無(wú)法獲取到線(xiàn)程

調(diào)用程序可以通過(guò)
isResponseFromFallback()查詢(xún)結(jié)果是由run()/construct()還是getFallback()/resumeWithFallback()返回的
4、隔離策略
hystrix提供了兩種隔離策略:線(xiàn)程池隔離和信號(hào)量隔離。hystrix默認(rèn)采用線(xiàn)程池隔離。
線(xiàn)程池隔離:不同服務(wù)通過(guò)使用不同線(xiàn)程池,彼此間將不受影響,達(dá)到隔離效果。以demo為例,我們通過(guò)andThreadPoolKey配置使用命名為
ThreadPoolTest的線(xiàn)程池,實(shí)現(xiàn)與其他命名的線(xiàn)程池天然隔離,如果不配置andThreadPoolKey則使用withGroupKey配置來(lái)命名線(xiàn)程池信號(hào)量隔離:線(xiàn)程隔離會(huì)帶來(lái)線(xiàn)程開(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)建新線(xiàn)程執(zhí)行run()/construct(),所以調(diào)用程序需要自己創(chuàng)建多個(gè)線(xiàn)程來(lái)模擬并發(fā)調(diào)用execute(),最后看到一旦并發(fā)線(xiàn)程>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ò)訪(fǎng)問(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è)線(xiàn)程,說(shuō)明這5個(gè)請(qǐng)求被合并了,而f6由另一個(gè)線(xiàn)程執(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):采用線(xiàn)程隔離,ExecutionIsolationStrategy.THREAD
private final HystrixProperty executionIsolationStrategy;
//使用線(xiàn)程隔離時(shí),調(diào)用超時(shí)時(shí)間,默認(rèn):1秒
private final HystrixProperty executionIsolationThreadTimeoutInMilliseconds;
//線(xiàn)程池的key,用于決定命令在哪個(gè)線(xiàn)程池執(zhí)行
private final HystrixProperty executionIsolationThreadPoolKeyOverride;
//是否開(kāi)啟fallback降級(jí)策略 默認(rèn):true
private final HystrixProperty fallbackEnabled;
// 使用線(xiàn)程隔離時(shí),是否對(duì)命令執(zhí)行超時(shí)的線(xiàn)程調(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
/* 配置線(xiàn)程池大小,默認(rèn)值10個(gè) */
private final HystrixProperty corePoolSize;
/* 配置線(xiàn)程值等待隊(duì)列長(zhǎng)度,默認(rèn)值:-1 建議值:-1表示不等待直接拒絕,測(cè)試表明線(xiàn)程池使用直接決絕策略+ 合適大小的非回縮線(xiàn)程池效率最高.所以不建議修改此值。 當(dāng)使用非回縮線(xiàn)程池時(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