用 Hystrix 構(gòu)建高可用服務(wù)架構(gòu)(上)

1.Hystrix 是什么?

在分布式系統(tǒng)中迹栓,每個服務(wù)都可能會調(diào)用很多其他服務(wù)荐虐,被調(diào)用的那些服務(wù)就是依賴服務(wù)藕夫,有的時候某些依賴服務(wù)出現(xiàn)故障也是很正常的。

Hystrix 可以讓我們在分布式系統(tǒng)中對服務(wù)間的調(diào)用進(jìn)行控制渴语,加入一些調(diào)用延遲或者依賴故障的容錯機(jī)制苹威。

Hystrix 通過將依賴服務(wù)進(jìn)行資源隔離,進(jìn)而阻止某個依賴服務(wù)出現(xiàn)故障時在整個系統(tǒng)所有的依賴服務(wù)調(diào)用中進(jìn)行蔓延驾凶;同時 Hystrix 還提供故障時的 fallback 降級機(jī)制牙甫。

總而言之掷酗,Hystrix 通過這些方法幫助我們提升分布式系統(tǒng)的可用性和穩(wěn)定性。

Hystrix 的歷史

Hystrix 是高可用性保障的一個框架腹暖。Netflix(可以認(rèn)為是國外的優(yōu)酷或者愛奇藝之類的視頻網(wǎng)站)的 API團(tuán)隊從 2011 年開始做一些提升系統(tǒng)可用性和穩(wěn)定性的工作汇在,Hystrix 就是從那時候開始發(fā)展出來的。

在 2012 年的時候脏答,Hystrix 就變得比較成熟和穩(wěn)定了糕殉,Netflix 中,除了 API 團(tuán)隊以外殖告,很多其他的團(tuán)隊都開始使用 Hystrix阿蝶。

時至今日,Netflix 中每天都有數(shù)十億次的服務(wù)間調(diào)用黄绩,通過 Hystrix 框架在進(jìn)行羡洁,而 Hystrix 也幫助Netflix 網(wǎng)站提升了整體的可用性和穩(wěn)定性。

2018 年 11 月爽丹,Hystrix 在其 Github 主頁宣布筑煮,不再開放新功能,推薦開發(fā)者使用其他仍然活躍的開源

項目粤蝎。維護(hù)模式的轉(zhuǎn)變絕不意味著 Hystrix 不再有價值真仲。相反,Hystrix 激發(fā)了很多偉大的想法和項目初澎,我們高可用的這一塊知識還是會針對 Hystrix 進(jìn)行講解秸应。

Hystrix 的設(shè)計原則

? ? ? ? ?對依賴服務(wù)調(diào)用時出現(xiàn)的調(diào)用延遲和調(diào)用失敗進(jìn)行控制和容錯保護(hù)。

????????在復(fù)雜的分布式系統(tǒng)中碑宴,阻止某一個依賴服務(wù)的故障在整個系統(tǒng)中蔓延软啼。比如某一個服務(wù)故障了,導(dǎo)致其它服務(wù)也跟著故障延柠。

????????提供 fail-fast(快速失敾雠病)和快速恢復(fù)的支持。

????????提供 fallback 優(yōu)雅降級的支持贞间。

????????支持近實時的監(jiān)控匕积、報警以及運維操作。

舉個栗子榜跌。

有這樣一個分布式系統(tǒng)闪唆,服務(wù) A 依賴于服務(wù) B,服務(wù) B 依賴于服務(wù) C/D/E钓葫。在這樣一個成熟的系統(tǒng)內(nèi)悄蕾,比如說最多可能只有 100 個線程資源。正常情況下,40 個線程并發(fā)調(diào)用服務(wù) C帆调,各 30 個線程并發(fā)調(diào)用D/E奠骄。

調(diào)用服務(wù) C,只需要 20ms番刊,現(xiàn)在因為服務(wù) C 故障了含鳞,比如延遲,或者掛了芹务,此時線程會 hang 住 2s 左右蝉绷。40 個線程全部被卡住,由于請求不斷涌入枣抱,其它的線程也用來調(diào)用服務(wù) C熔吗,同樣也會被卡住。這樣導(dǎo)致服務(wù) B 的線程資源被耗盡佳晶,無法接收新的請求桅狠,甚至可能因為大量線程不斷的運轉(zhuǎn),導(dǎo)致自己宕機(jī)轿秧。服務(wù) A 也掛中跌。

Hystrix 可以對其進(jìn)行資源隔離,比如限制服務(wù) B 只有 40 個線程調(diào)用服務(wù) C菇篡。當(dāng)此 40 個線程被 hang住時晒他,其它 60 個線程依然能正常調(diào)用工作。從而確保整個系統(tǒng)不會被拖垮逸贾。

Hystrix 更加細(xì)節(jié)的設(shè)計原則

????????????????阻止任何一個依賴服務(wù)耗盡所有的資源,比如 tomcat 中的所有線程資源津滞。

????????????????避免請求排隊和積壓铝侵,采用限流和 fail fast 來控制故障。

????????????????提供 fallback 降級機(jī)制來應(yīng)對故障触徐。

????????????????使用資源隔離技術(shù)咪鲜,比如 bulkhead(艙壁隔離技術(shù))、swimlane(泳道技術(shù))撞鹉、circuit breaker(斷路技術(shù))來限制任何一個依賴服務(wù)的故障的影響疟丙。

????????????????通過近實時的統(tǒng)計/監(jiān)控/報警功能,來提高故障發(fā)現(xiàn)的速度鸟雏。

????????????????通過近實時的屬性和配置熱修改功能享郊,來提高故障處理和恢復(fù)的速度。

????????????????保護(hù)依賴服務(wù)調(diào)用的所有故障情況孝鹊,而不僅僅只是網(wǎng)絡(luò)故障情況炊琉。


2.基于 Hystrix 線程池技術(shù)實現(xiàn)資源隔離

上一講提到,如果從 Nginx 開始,緩存都失效了苔咪,Nginx 會直接通過緩存服務(wù)調(diào)用商品服務(wù)獲取最新商品數(shù)據(jù)(我們基于電商項目做個討論)锰悼,有可能出現(xiàn)調(diào)用延時而把緩存服務(wù)資源耗盡的情況。這里团赏,我們就來說說箕般,怎么通過 Hystrix 線程池技術(shù)實現(xiàn)資源隔離。

資源隔離舔清,就是說丝里,你如果要把對某一個依賴服務(wù)的所有調(diào)用請求,全部隔離在同一份資源池內(nèi)鸠踪,不會去用其它資源了丙者,這就叫資源隔離。哪怕對這個依賴服務(wù)营密,比如說商品服務(wù)械媒,現(xiàn)在同時發(fā)起的調(diào)用量已經(jīng)到了 1000,但是線程池內(nèi)就 10 個線程评汰,最多就只會用這 10 個線程去執(zhí)行纷捞,不會說,對商品服務(wù)的請求被去,因為接口調(diào)用延時主儡,將 tomcat 內(nèi)部所有的線程資源全部耗盡。

Hystrix 進(jìn)行資源隔離惨缆,其實是提供了一個抽象糜值,叫做 command。這也是 Hystrix 最最基本的資源隔離技術(shù)坯墨。


利用 HystrixCommand 獲取單條數(shù)據(jù)

我們通過將調(diào)用商品服務(wù)的操作封裝在 HystrixCommand 中寂汇,限定一個 key,比如下面的 GetProductInfoCommandGroup捣染,在這里我們可以簡單認(rèn)為這是一個線程池骄瓣,每次調(diào)用商品服務(wù),就只會用該線程池中的資源耍攘,不會再去用其它線程資源了榕栏。

public class GetProductInfoCommand extends HystrixCommand<ProductInfo> {

private Long productId;

public GetProductInfoCommand(Long productId) {

super(HystrixCommandGroupKey.Factory.asKey("GetProductInfoCommandGroup"));

this.productId = productId;

}

@Override

protected ProductInfo run() {

String url = "http://localhost:8081/getProductInfo?productId=" + productId;

// 調(diào)用商品服務(wù)接口

String response = HttpClientUtils.sendGetRequest(url);

return JSONObject.parseObject(response, ProductInfo.class);

}

}

我們在緩存服務(wù)接口中,根據(jù) productId 創(chuàng)建 command 并執(zhí)行蕾各,獲取到商品數(shù)據(jù)扒磁。

@RequestMapping("/getProductInfo")@ResponseBodypublic String getProductInfo(Long productId) {

HystrixCommand<ProductInfo> getProductInfoCommand = new GetProductInfoCommand(productId);

// 通過 command 執(zhí)行,獲取最新商品數(shù)據(jù)

ProductInfo productInfo = getProductInfoCommand.execute();

System.out.println(productInfo);

return "success";

}

上面執(zhí)行的是 execute() 方法式曲,其實是同步的渗磅。也可以對 command 調(diào)用 queue() 方法,它僅僅是將command 放入線程池的一個等待隊列,就立即返回始鱼,拿到一個 Future 對象仔掸,后面可以繼續(xù)做其它一些事情,然后過一段時間對 Future 調(diào)用 get() 方法獲取數(shù)據(jù)医清。這是異步的起暮。


利用 HystrixObservableCommand 批量獲取數(shù)據(jù)

只要是獲取商品數(shù)據(jù),全部都綁定到同一個線程池里面去会烙,我們通過 HystrixObservableCommand 的一個線程去執(zhí)行负懦,而在這個線程里面,批量把多個 productId 的 productInfo 拉回來柏腻。

public class GetProductInfosCommand extends HystrixObservableCommand<ProductInfo> {

private String[] productIds;

public GetProductInfosCommand(String[] productIds) {

// 還是綁定在同一個線程池

super(HystrixCommandGroupKey.Factory.asKey("GetProductInfoGroup"));

this.productIds = productIds;

}

@Override

protected Observable<ProductInfo> construct() {

return Observable.unsafeCreate((Observable.OnSubscribe<ProductInfo>) subscriber -> {

for (String productId : productIds) {

// 批量獲取商品數(shù)據(jù)

String url = "http://localhost:8081/getProductInfo?productId=" + productId;

String response = HttpClientUtils.sendGetRequest(url);

ProductInfo productInfo = JSONObject.parseObject(response, ProductInfo.class);

subscriber.onNext(productInfo);

}

subscriber.onCompleted();

}).subscribeOn(Schedulers.io());

}

}

在緩存服務(wù)接口中纸厉,根據(jù)傳來的 id 列表,比如是以 , 分隔的 id 串五嫂,通過上面的HystrixObservableCommand颗品,執(zhí)行 Hystrix 的一些 API 方法,獲取到所有商品數(shù)據(jù)沃缘。

public String getProductInfos(String productIds) {

String[] productIdArray = productIds.split(",");

HystrixObservableCommand<ProductInfo> getProductInfosCommand = new

GetProductInfosCommand(productIdArray);

Observable<ProductInfo> observable = getProductInfosCommand.observe();

observable.subscribe(new Observer<ProductInfo>() {

@Override

public void onCompleted() {

System.out.println("獲取完了所有的商品數(shù)據(jù)");

}

@Override

public void onError(Throwable e) {

e.printStackTrace();

}

/** * 獲取完一條數(shù)據(jù)躯枢,就回調(diào)一次這個方法 * @param productInfo

*/

@Override

public void onNext(ProductInfo productInfo) {

System.out.println(productInfo);

}

});

return "success";

}

我們回過頭來,看看 Hystrix 線程池技術(shù)是如何實現(xiàn)資源隔離的槐臀。

從 Nginx 開始锄蹂,緩存都失效了,那么 Nginx 通過緩存服務(wù)去調(diào)用商品服務(wù)水慨。緩存服務(wù)默認(rèn)的線程大小是 10個得糜,最多就只有 10 個線程去調(diào)用商品服務(wù)的接口。即使商品服務(wù)接口故障了晰洒,最多就只有 10 個線程會hang 死在調(diào)用商品服務(wù)接口的路上朝抖,緩存服務(wù)的 tomcat 內(nèi)其它的線程還是可以用來調(diào)用其它的服務(wù),干其它的事情欢顷。


3.基于 Hystrix 信號量機(jī)制實現(xiàn)資源隔離

Hystrix 里面核心的一項功能,其實就是所謂的資源隔離捉蚤,要解決的最最核心的問題抬驴,就是將多個依賴服務(wù)的調(diào)用分別隔離到各自的資源池內(nèi)。避免說對某一個依賴服務(wù)的調(diào)用缆巧,因為依賴服務(wù)的接口調(diào)用的延遲或者失敗布持,導(dǎo)致服務(wù)所有的線程資源全部耗費在這個服務(wù)的接口調(diào)用上。一旦說某個服務(wù)的線程資源全部耗盡的話陕悬,就可能導(dǎo)致服務(wù)崩潰题暖,甚至說這種故障會不斷蔓延。

Hystrix 實現(xiàn)資源隔離,主要有兩種技術(shù):

????????????線程池

????????????信號量

默認(rèn)情況下胧卤,Hystrix 使用線程池模式唯绍。

前面已經(jīng)說過線程池技術(shù)了,這一小節(jié)就來說說信號量機(jī)制實現(xiàn)資源隔離枝誊,以及這兩種技術(shù)的區(qū)別與具體應(yīng)用場景况芒。

信號量機(jī)制

信號量的資源隔離只是起到一個開關(guān)的作用,比如叶撒,服務(wù) A 的信號量大小為 10绝骚,那么就是說它同時只允許有 10 個 tomcat 線程來訪問服務(wù) A,其它的請求都會被拒絕祠够,從而達(dá)到資源隔離和限流保護(hù)的作用压汪。

線程池與信號量區(qū)別

線程池隔離技術(shù),并不是說去控制類似 tomcat 這種 web 容器的線程古瓤。更加嚴(yán)格的意義上來說止剖,Hystrix 的線程池隔離技術(shù),控制的是 tomcat 線程的執(zhí)行湿滓。Hystrix 線程池滿后滴须,會確保說,tomcat 的線程不會因為依賴服務(wù)的接口調(diào)用延遲或故障而被 hang 住叽奥,tomcat 其它的線程不會卡死扔水,可以快速返回,然后支撐其它的事情朝氓。

線程池隔離技術(shù)魔市,是用 Hystrix 自己的線程去執(zhí)行調(diào)用;而信號量隔離技術(shù)赵哲,是直接讓 tomcat 線程去調(diào)用依賴服務(wù)待德。信號量隔離,只是一道關(guān)卡枫夺,信號量有多少将宪,就允許多少個 tomcat 線程通過它,然后去執(zhí)行橡庞。

適用場景:

? 線程池技術(shù)较坛,適合絕大多數(shù)場景,比如說我們對依賴服務(wù)的網(wǎng)絡(luò)請求的調(diào)用和訪問扒最、需要對調(diào)用的timeout 進(jìn)行控制(捕捉 timeout 超時異常)丑勤。

? 信號量技術(shù),適合說你的訪問不是對外部依賴的訪問吧趣,而是對內(nèi)部的一些比較復(fù)雜的業(yè)務(wù)邏輯的訪問法竞,并且系統(tǒng)內(nèi)部的代碼耙厚,其實不涉及任何的網(wǎng)絡(luò)請求,那么只要做信號量的普通限流就可以了岔霸,因為不需要去捕獲 timeout 類似的問題薛躬。

信號量簡單 Demo

業(yè)務(wù)背景里,比較適合信號量的是什么場景呢秉剑?

比如說泛豪,我們一般來說,緩存服務(wù)侦鹏,可能會將一些量特別少诡曙、訪問又特別頻繁的數(shù)據(jù),放在自己的純內(nèi)存中略水。

舉個栗子价卤。一般我們在獲取到商品數(shù)據(jù)之后,都要去獲取商品是屬于哪個地理位置渊涝、省慎璧、市、賣家等跨释,可能在自己的純內(nèi)存中胸私,比如就一個 Map 去獲取。對于這種直接訪問本地內(nèi)存的邏輯鳖谈,比較適合用信號量做一下簡單的隔離岁疼。

優(yōu)點在于,不用自己管理線程池啦缆娃,不用 care timeout 超時啦捷绒,也不需要進(jìn)行線程的上下文切換啦。信號量做隔離的話贯要,性能相對來說會高一些暖侨。

假如這是本地緩存,我們可以通過 cityId崇渗,拿到 cityName字逗。

public class LocationCache {

private static Map<Long, String> cityMap = new HashMap<>();

static {

cityMap.put(1L, "北京");

}

/** * 通過 cityId 獲取 cityName * * @param cityId 城市 id * @return 城市名

*/

public static String getCityName(Long cityId) {

return cityMap.get(cityId);

}

}

寫一個 GetCityNameCommand,策略設(shè)置為信號量宅广。run() 方法中獲取本地緩存葫掉。我們目的就是對獲取本地緩存的代碼進(jìn)行資源隔離。

public class GetCityNameCommand extends HystrixCommand<String> {

private Long cityId;

public GetCityNameCommand(Long cityId) {

// 設(shè)置信號量隔離策略

super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GetCityNameGroup"))

.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()

.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIs

olationStrategy.SEMAPHORE)));

this.cityId = cityId;

}

@Override

protected String run() {

// 需要進(jìn)行信號量隔離的代碼

return LocationCache.getCityName(cityId);

}

}

在接口層乘碑,通過創(chuàng)建 GetCityNameCommand挖息,傳入 cityId金拒,執(zhí)行 execute() 方法兽肤,那么獲取本地 cityName緩存的代碼將會進(jìn)行信號量的資源隔離套腹。

@RequestMapping("/getProductInfo")@ResponseBodypublic String getProductInfo(Long productId) {

HystrixCommand<ProductInfo> getProductInfoCommand = new GetProductInfoCommand(productId);

// 通過 command 執(zhí)行,獲取最新商品數(shù)據(jù)

ProductInfo productInfo = getProductInfoCommand.execute();

Long cityId = productInfo.getCityId();

GetCityNameCommand getCityNameCommand = new GetCityNameCommand(cityId);

// 獲取本地內(nèi)存(cityName)的代碼會被信號量進(jìn)行資源隔離

String cityName = getCityNameCommand.execute();

productInfo.setCityName(cityName);

System.out.println(productInfo);

return "success";

}


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末资铡,一起剝皮案震驚了整個濱河市电禀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌笤休,老刑警劉巖尖飞,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異店雅,居然都是意外死亡政基,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門闹啦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沮明,“玉大人,你說我怎么就攤上這事窍奋〖鼋。” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵琳袄,是天一觀的道長江场。 經(jīng)常有香客問我,道長窖逗,這世上最難降的妖魔是什么址否? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮滑负,結(jié)果婚禮上在张,老公的妹妹穿的比我還像新娘。我一直安慰自己矮慕,他們只是感情好帮匾,可當(dāng)我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著痴鳄,像睡著了一般瘟斜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上痪寻,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天螺句,我揣著相機(jī)與錄音,去河邊找鬼橡类。 笑死蛇尚,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的顾画。 我是一名探鬼主播取劫,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼匆笤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了谱邪?” 一聲冷哼從身側(cè)響起炮捧,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎惦银,沒想到半個月后咆课,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡扯俱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年书蚪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片迅栅。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡善炫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出库继,到底是詐尸還是另有隱情箩艺,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布宪萄,位于F島的核電站艺谆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏拜英。R本人自食惡果不足惜静汤,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望居凶。 院中可真熱鬧虫给,春花似錦、人聲如沸侠碧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽弄兜。三九已至药蜻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間替饿,已是汗流浹背语泽。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留视卢,地道東北人踱卵。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像据过,于是被迫代替她去往敵國和親惋砂。 傳聞我的和親對象是個殘疾皇子蔬充,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,779評論 2 354