一、hystrix 產(chǎn)生背景
微服務(wù)是解決復(fù)雜服務(wù)的一個(gè)方案,在功能不變的情況下,對(duì)一個(gè)復(fù)雜的單體服務(wù)分解為多個(gè)可管理的分支。每個(gè)服務(wù)作為輕量的子服務(wù),通過(guò)RPC實(shí)現(xiàn)服務(wù)間的關(guān)聯(lián),將服務(wù)簡(jiǎn)單化。每個(gè)服務(wù)根據(jù)自己的需要選擇技術(shù)棧,互不影響,方便開(kāi)發(fā)、維護(hù)。例如S劃分為a,b,c。微服務(wù)的好處是有效的拆分應(yīng)用,實(shí)現(xiàn)敏捷開(kāi)發(fā)和部署。
微服務(wù)一系列優(yōu)勢(shì)下,也給微服務(wù)的管理和穩(wěn)定性帶來(lái)挑戰(zhàn),比如一個(gè)服務(wù)依賴30個(gè)微服務(wù),每個(gè)微服務(wù)的可用性是99.999%,在不加任何管理的情況下,該聚合服務(wù)的可用性將是99.999%的30次方=99.97%,系統(tǒng)的可用性直接降了兩個(gè)數(shù)量級(jí)達(dá)到三個(gè)九。
且由于依賴的傳遞性,很容易產(chǎn)生雪崩效應(yīng)。如下圖所示:
一個(gè)應(yīng)用中,任意一個(gè)點(diǎn)的不可用或者響應(yīng)延時(shí)都有可能造成服務(wù)不可用
更可怕的是,被hang住的請(qǐng)求會(huì)很快耗盡系統(tǒng)的資源,當(dāng)該類(lèi)請(qǐng)求越來(lái)越多,占用的計(jì)算機(jī)資源越來(lái)越多的時(shí)候,會(huì)導(dǎo)致系統(tǒng)瓶頸出現(xiàn),造成其他的請(qǐng)求同樣不可用,最終導(dǎo)致業(yè)務(wù)系統(tǒng)崩潰,又稱:雪崩效應(yīng)
造成雪崩原因可以歸結(jié)為以下三個(gè):
- 服務(wù)提供者不可用(硬件故障,程序Bug,緩存擊穿,用戶大量請(qǐng)求)
- 重試加大流量(用戶重試,代碼邏輯重試)
- 服務(wù)調(diào)用者不可用(同步等待造成的資源耗盡)
- 最終的結(jié)果就是一個(gè)服務(wù)不可用導(dǎo)致一系列服務(wù)的不可用,而往往這種后果往往無(wú)法預(yù)料的。
二、 hystrix實(shí)現(xiàn)原理
hystrix語(yǔ)義為“豪豬”,具有自我保護(hù)的能力。hystrix的出現(xiàn)即為解決雪崩效應(yīng),它通過(guò)四個(gè)方面的機(jī)制來(lái)解決這個(gè)問(wèn)題
- 隔離(線程池隔離和信號(hào)量隔離):限制調(diào)用分布式服務(wù)的資源使用,某一個(gè)調(diào)用的服務(wù)出現(xiàn)問(wèn)題不會(huì)影響其他服務(wù)調(diào)用。
- 優(yōu)雅的降級(jí)機(jī)制:超時(shí)降級(jí)、資源不足時(shí)(線程或信號(hào)量)降級(jí),降級(jí)后可以配合降級(jí)接口返回托底數(shù)據(jù)。
- 融斷:當(dāng)失敗率達(dá)到閥值自動(dòng)觸發(fā)降級(jí)(如因網(wǎng)絡(luò)故障/超時(shí)造成的失敗率高),熔斷器觸發(fā)的快速失敗會(huì)進(jìn)行快速恢復(fù)。
- 緩存:提供了請(qǐng)求緩存、請(qǐng)求合并實(shí)現(xiàn)。
- 支持實(shí)時(shí)監(jiān)控、報(bào)警、控制(修改配置)
2.1 隔離
(1)線程池隔離模式:使用一個(gè)線程池來(lái)存儲(chǔ)當(dāng)前的請(qǐng)求,線程池對(duì)請(qǐng)求作處理,設(shè)置任務(wù)返回處理超時(shí)時(shí)間,堆積的請(qǐng)求堆積入線程池隊(duì)列。這種方式需要為每個(gè)依賴的服務(wù)申請(qǐng)線程池,有一定的資源消耗,好處是可以應(yīng)對(duì)突發(fā)流量(流量洪峰來(lái)臨時(shí),處理不完可將數(shù)據(jù)存儲(chǔ)到線程池隊(duì)里慢慢處理)
(2)信號(hào)量隔離模式:使用一個(gè)原子計(jì)數(shù)器(或信號(hào)量)來(lái)記錄當(dāng)前有多少個(gè)線程在運(yùn)行,請(qǐng)求來(lái)先判斷計(jì)數(shù)器的數(shù)值,若超過(guò)設(shè)置的最大線程個(gè)數(shù)則丟棄改類(lèi)型的新請(qǐng)求,若不超過(guò)則執(zhí)行計(jì)數(shù)操作請(qǐng)求來(lái)計(jì)數(shù)器+1,請(qǐng)求返回計(jì)數(shù)器-1。這種方式是嚴(yán)格的控制線程且立即返回模式,無(wú)法應(yīng)對(duì)突發(fā)流量(流量洪峰來(lái)臨時(shí),處理的線程超過(guò)數(shù)量,其他的請(qǐng)求會(huì)直接返回,不繼續(xù)去請(qǐng)求依賴的服務(wù))
區(qū)別(兩種隔離方式只能選其一):
線程池隔離 | 信號(hào)量隔離 | |
---|---|---|
線程 | 與調(diào)用線程非相同線程 | 與調(diào)用線程相同(jetty線程) |
開(kāi)銷(xiāo) | 排隊(duì)、調(diào)度、上下文開(kāi)銷(xiāo)等 | 無(wú)線程切換,開(kāi)銷(xiāo)低 |
異步 | 支持 | 不支持 |
并發(fā)支持 | 支持(最大線程池大?。?/td> | 支持(最大信號(hào)量上限) |
2.2 融斷
正常狀態(tài)下,電路處于關(guān)閉狀態(tài)(Closed),如果調(diào)用持續(xù)出錯(cuò)或者超時(shí),電路被打開(kāi)進(jìn)入熔斷狀態(tài)(Open),后續(xù)一段時(shí)間內(nèi)的所有調(diào)用都會(huì)被拒絕(Fail Fast),一段時(shí)間以后,保護(hù)器會(huì)嘗試進(jìn)入半熔斷狀態(tài)(Half-Open),允許少量請(qǐng)求進(jìn)來(lái)嘗試,如果調(diào)用仍然失敗,則回到熔斷狀態(tài),如果調(diào)用成功,則回到電路閉合狀態(tài);
HystrixCircuitBreaker(斷路器的具體實(shí)現(xiàn)):
詳細(xì)的工作流程:http://hot66hot.iteye.com/blog/2155036
2.3 降級(jí)
可能大家會(huì)混淆“融斷”和“降級(jí)”兩個(gè)概念。
在股票市場(chǎng),熔斷這個(gè)詞大家都不陌生,是指當(dāng)股指波幅達(dá)到某個(gè)點(diǎn)后,交易所為控制風(fēng)險(xiǎn)采取的暫停交易措施。相應(yīng)的,服務(wù)熔斷一般是指軟件系統(tǒng)中,由于某些原因使得服務(wù)出現(xiàn)了過(guò)載現(xiàn)象,為防止造成整個(gè)系統(tǒng)故障,從而采用的一種保護(hù)措施,所以很多地方把熔斷亦稱為過(guò)載保護(hù)。
大家都見(jiàn)過(guò)女生旅行吧,大號(hào)的旅行箱是必備物,平常走走近處綽綽有余,但一旦出個(gè)遠(yuǎn)門(mén),再大的箱子都白搭了,怎么辦呢?常見(jiàn)的情景就是把物品拿出來(lái)分分堆,比了又比,最后一些非必需品的就忍痛放下了,等到下次箱子夠用了,再帶上用一用。而服務(wù)降級(jí),就是這么回事,整體資源快不夠了,忍痛將某些服務(wù)先關(guān)掉,待渡過(guò)難關(guān),再開(kāi)啟回來(lái)。
二者的目標(biāo)是一致的,目的都是保證上游服務(wù)的穩(wěn)定性。但其關(guān)注的重點(diǎn)并不一樣,融斷對(duì)下層依賴的服務(wù)并不級(jí)(或者說(shuō)孰輕孰重),一旦產(chǎn)生故障就斷掉;而降級(jí)需要對(duì)下層依賴的業(yè)務(wù)分級(jí),把產(chǎn)生故障的丟了,換一個(gè)輕量級(jí)的方案,是一種退而求其次的方法。
根據(jù)業(yè)務(wù)場(chǎng)景的不同,一般采用以下兩種模式:
第一種(最常用)如果服務(wù)失敗,則我們通過(guò)fallback進(jìn)行降級(jí),返回靜態(tài)值。
第二種采用服務(wù)級(jí)聯(lián)的模式,如果第一個(gè)服務(wù)失敗,則調(diào)用備用服務(wù),例如失敗重試或者訪問(wèn)緩存失敗再去取數(shù)據(jù)庫(kù)。服務(wù)級(jí)聯(lián)的目的則是盡最大努力保證返回?cái)?shù)據(jù)的成功性,但如果考慮不充分,則有可能導(dǎo)致級(jí)聯(lián)的服務(wù)崩潰(比如,緩存失敗了,把全部流量打到數(shù)據(jù)庫(kù),瞬間導(dǎo)致數(shù)據(jù)庫(kù)掛掉)。因此級(jí)聯(lián)模式,也要慎用,增加了管理的難度。
2.4 緩存
不建議使用,對(duì)問(wèn)題排查會(huì)造成很大的困擾,因此也不在這里講了
三、hystrix應(yīng)用
hystrix的運(yùn)行流程如下所示:
- 兩個(gè)核心代理HystrixCommand,HystrixObservableCommand,任何依賴的服務(wù)只需要繼承這兩個(gè)類(lèi)就可以了。其中HystrixObservableCommand使用觀察者模式(不在此介紹范圍之內(nèi),了解請(qǐng)移步RxJava)
- HystrixCommand 可以采用同步調(diào)用和異步調(diào)用,異步返回Future對(duì)象(還未直接支持CompletebleFuture)
如果開(kāi)啟了緩存,則會(huì)根據(jù)GroupKey,Commandkey以及cachedKey確定是否存在緩存(不建議使用) - 判斷斷路器是否開(kāi)啟,開(kāi)啟則直接調(diào)用getFallback,
- 判斷是否滿足信號(hào)量隔離或線程池隔離的條件,如果隔離則拋異常
- 執(zhí)行run方法
- metrics包含了一個(gè)計(jì)數(shù)器,用來(lái)計(jì)算當(dāng)前服務(wù)的狀態(tài),無(wú)論是成功調(diào)用,還是拋異常都會(huì)記錄數(shù)據(jù)(接下來(lái)再詳細(xì)講)
- 執(zhí)行降級(jí)策略
3.1 代碼實(shí)現(xiàn)
public class GetInfoFromSinaiCommand extends HystrixCommand<List<PoiInfo>> {
private PoiClient poiClient;
private List<Integer> poiIds;
private static final List<String> FIELDS = ImmutableList.of("id", "cate", "subcate");
public GetInfoFromSinaiCommand(PoiClient poiClient, List<Integer> poiIds) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("sinai"))
//command配置
.andCommandKey(HystrixCommandKey.Factory.asKey("GetInfoFromSinaiCommand"))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withRequestCacheEnabled(true))
//融斷器配置
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withCircuitBreakerEnabled(true))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withCircuitBreakerRequestVolumeThreshold(20))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withCircuitBreakerSleepWindowInMilliseconds(5000))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withCircuitBreakerErrorThresholdPercentage(50))
//ThreadPool配置
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("GetInfoFromSinaiCommand"))
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(10))
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(-1))
);
this.poiClient = poiClient;
this.poiIds = poiIds;
}
@Override
public List<PoiInfo> run() throws Exception {
if (poiIds.isEmpty()) {
return Lists.newArrayList();
}
List<PoiModel> pioModels = poiClient.listPois(poiIds, FIELDS);
return parseResult(pioModels);
}
@Override
protected String getCacheKey() {
return String.valueOf(poiIds);
}
@Override
protected List<PoiInfo> getFallback() {
return Lists.newArrayList();
}
private List<PoiInfo> parseResult(List<PoiModel> poiModels) {
if (poiModels == null || poiModels.isEmpty()) {
return Lists.newArrayList();
}
List<PoiInfo> res = Lists.newArrayList();
for (PoiModel poiModel : poiModels) {
PoiInfo poiInfo = new PoiInfo();
poiInfo.setPoiId(poiModel.getId());
if (poiModel.getCate() != null) {
poiInfo.setCate(poiModel.getCate());
}
if (poiModel.getSubcate() != null) {
poiInfo.setSubcate(poiModel.getSubcate());
}
res.add(poiInfo);
}
return res;
}
}
3.2 參數(shù)說(shuō)明
|參數(shù)類(lèi)型|參數(shù)名|默認(rèn)值|說(shuō)明|
|---|---|---|---|---|
|command配置|executionIsolationStrategy|ExecutionIsolationStrategy.THREAD|信號(hào)隔離或線程隔離,默認(rèn):采用線程隔離,|
|| executionIsolationThreadTimeoutInMillisecond |1s|隔離時(shí)間大,即多長(zhǎng)時(shí)間后進(jìn)行重試|
|| executionIsolationSemaphoreMaxConcurrentRequests |10|使用信號(hào)量隔離時(shí),命令調(diào)用最大的并發(fā)數(shù),默認(rèn):10 |
| |fallbackIsolationSemaphoreMaxConcurrentRequests |10|使用信號(hào)量隔離時(shí),命令fallback(降級(jí))調(diào)用最大的并發(fā)數(shù),默認(rèn):10|
|| fallbackEnabled |true|是否開(kāi)啟fallback降級(jí)策略|
|| executionIsolationThreadInterruptOnTimeout |true|使用線程隔離時(shí),是否對(duì)命令執(zhí)行超時(shí)的線程調(diào)用中斷(Thread.interrupt())操作|
|| metricsRollingStatisticalWindowInMilliseconds |10000ms|統(tǒng)計(jì)滾動(dòng)的時(shí)間窗口,默認(rèn):10s|
|| metricsRollingStatisticalWindowBuckets |10|統(tǒng)計(jì)窗口的Buckets的數(shù)量,默認(rèn):10個(gè)
|| metricsRollingPercentileEnabled |true|是否開(kāi)啟監(jiān)控統(tǒng)計(jì)功能,默認(rèn):true|
|| requestLogEnabled |true|是否開(kāi)啟請(qǐng)求日志|
|| requestCacheEnabled |true|是否開(kāi)啟請(qǐng)求緩存|
|熔斷器配置|circuitBreakerRequestVolumeThreshold|20|主要用在小流量|
|| circuitBreakerSleepWindowInMilliseconds | 5000ms |熔斷器默認(rèn)工作時(shí)間,默認(rèn):5秒.熔斷器中斷請(qǐng)求5秒后會(huì)進(jìn)入半打開(kāi)狀態(tài),放部分流量過(guò)去重試|
|| circuitBreakerEnabled | true |是否啟用熔斷器,默認(rèn)true. 啟動(dòng) |
|| circuitBreakerErrorThresholdPercentage | 50 |默認(rèn):50%。當(dāng)出錯(cuò)率超過(guò)50%后熔斷器啟動(dòng)|
|| circuitBreakerForceOpen | false |是否強(qiáng)制開(kāi)啟熔斷器阻斷所有請(qǐng)求,默認(rèn):false,不開(kāi)啟|
|| circuitBreakerForceClosed | false |是否允許熔斷器忽略錯(cuò)誤,默認(rèn)false, 不開(kāi)啟|
|線程池配置|HystrixThreadPoolProperties.Setter().withCoreSize(int value)|10|配置線程池大小,默認(rèn)值10個(gè)|
||HystrixThreadPoolProperties.Setter().withMaxQueueSize(int value)|-1|配置線程值等待隊(duì)列長(zhǎng)度|
3.3監(jiān)控上報(bào)
參考文章:
本文的很多圖和文字都粘貼自網(wǎng)上文章,沒(méi)有注明引用請(qǐng)包涵!如有任何問(wèn)題請(qǐng)留言或者加群,我會(huì)及時(shí)回復(fù)
http://zhuanlan.51cto.com/art/201704/536307.htm