熔斷器 Hystrix 的原理與使用

前言

分布式系統(tǒng)中經(jīng)常會出現(xiàn)某個基礎(chǔ)服務(wù)不可用造成整個系統(tǒng)不可用的情況, 這種現(xiàn)象被稱為服務(wù)雪崩效應(yīng). 為了應(yīng)對服務(wù)雪崩, 一種常見的做法是手動服務(wù)降級. 而Hystrix的出現(xiàn),給我們提供了另一種選擇.

服務(wù)雪崩效應(yīng)的定義

服務(wù)雪崩效應(yīng)是一種因 服務(wù)提供者 的不可用導(dǎo)致 服務(wù)調(diào)用者 的不可用,并將不可用 逐漸放大 的過程.如果所示:

image

上圖中, A為服務(wù)提供者, B為A的服務(wù)調(diào)用者, C和D是B的服務(wù)調(diào)用者. 當(dāng)A的不可用,引起B(yǎng)的不可用,并將不可用逐漸放大C和D時, 服務(wù)雪崩就形成了.

服務(wù)雪崩效應(yīng)形成的原因

我把服務(wù)雪崩的參與者簡化為 服務(wù)提供者服務(wù)調(diào)用者, 并將服務(wù)雪崩產(chǎn)生的過程分為以下三個階段來分析形成的原因:

  1. 服務(wù)提供者不可用

  2. 重試加大流量

  3. 服務(wù)調(diào)用者不可用

image

服務(wù)雪崩的每個階段都可能由不同的原因造成, 比如造成 服務(wù)不可用 的原因有:

  • 硬件故障

  • 程序Bug

  • 緩存擊穿

  • 用戶大量請求

硬件故障可能為硬件損壞造成的服務(wù)器主機(jī)宕機(jī), 網(wǎng)絡(luò)硬件故障造成的服務(wù)提供者的不可訪問.
緩存擊穿一般發(fā)生在緩存應(yīng)用重啟, 所有緩存被清空時,以及短時間內(nèi)大量緩存失效時. 大量的緩存不命中, 使請求直擊后端,造成服務(wù)提供者超負(fù)荷運(yùn)行,引起服務(wù)不可用.
在秒殺和大促開始前,如果準(zhǔn)備不充分,用戶發(fā)起大量請求也會造成服務(wù)提供者的不可用.

而形成 重試加大流量 的原因有:

  • 用戶重試

  • 代碼邏輯重試

在服務(wù)提供者不可用后, 用戶由于忍受不了界面上長時間的等待,而不斷刷新頁面甚至提交表單.
服務(wù)調(diào)用端的會存在大量服務(wù)異常后的重試邏輯.
這些重試都會進(jìn)一步加大請求流量.

最后, 服務(wù)調(diào)用者不可用 產(chǎn)生的主要原因是:

  • 同步等待造成的資源耗盡

當(dāng)服務(wù)調(diào)用者使用 同步調(diào)用 時, 會產(chǎn)生大量的等待線程占用系統(tǒng)資源. 一旦線程資源被耗盡,服務(wù)調(diào)用者提供的服務(wù)也將處于不可用狀態(tài), 于是服務(wù)雪崩效應(yīng)產(chǎn)生了.

服務(wù)雪崩的應(yīng)對策略

針對造成服務(wù)雪崩的不同原因, 可以使用不同的應(yīng)對策略:

  1. 流量控制

  2. 改進(jìn)緩存模式

  3. 服務(wù)自動擴(kuò)容

  4. 服務(wù)調(diào)用者降級服務(wù)

流量控制 的具體措施包括:

  • 網(wǎng)關(guān)限流

  • 用戶交互限流

  • 關(guān)閉重試

因?yàn)镹ginx的高性能, 目前一線互聯(lián)網(wǎng)公司大量采用Nginx+Lua的網(wǎng)關(guān)進(jìn)行流量控制, 由此而來的OpenResty也越來越熱門.

用戶交互限流的具體措施有: 1. 采用加載動畫,提高用戶的忍耐等待時間. 2. 提交按鈕添加強(qiáng)制等待時間機(jī)制.

改進(jìn)緩存模式 的措施包括:

  • 緩存預(yù)加載

  • 同步改為異步刷新

服務(wù)自動擴(kuò)容 的措施主要有:

  • AWS的auto scaling

服務(wù)調(diào)用者降級服務(wù) 的措施包括:

  • 資源隔離

  • 對依賴服務(wù)進(jìn)行分類

  • 不可用服務(wù)的調(diào)用快速失敗

資源隔離主要是對調(diào)用服務(wù)的線程池進(jìn)行隔離.

我們根據(jù)具體業(yè)務(wù),將依賴服務(wù)分為: 強(qiáng)依賴和若依賴. 強(qiáng)依賴服務(wù)不可用會導(dǎo)致當(dāng)前業(yè)務(wù)中止,而弱依賴服務(wù)的不可用不會導(dǎo)致當(dāng)前業(yè)務(wù)的中止.

不可用服務(wù)的調(diào)用快速失敗一般通過 超時機(jī)制, 熔斷器 和熔斷后的 降級方法 來實(shí)現(xiàn).

使用Hystrix預(yù)防服務(wù)雪崩

Hystrix [h?st'r?ks]的中文含義是豪豬, 因其背上長滿了刺,而擁有自我保護(hù)能力. Netflix的 Hystrix 是一個幫助解決分布式系統(tǒng)交互時超時處理和容錯的類庫, 它同樣擁有保護(hù)系統(tǒng)的能力.

Hystrix的設(shè)計原則包括:

  • 資源隔離

  • 熔斷器

  • 命令模式

資源隔離

貨船為了進(jìn)行防止漏水和火災(zāi)的擴(kuò)散,會將貨倉分隔為多個,

這種資源隔離減少風(fēng)險的方式被稱為:Bulkheads(艙壁隔離模式).
Hystrix將同樣的模式運(yùn)用到了服務(wù)調(diào)用者上.

在一個高度服務(wù)化的系統(tǒng)中,我們實(shí)現(xiàn)的一個業(yè)務(wù)邏輯通常會依賴多個服務(wù),比如:
商品詳情展示服務(wù)會依賴商品服務(wù), 價格服務(wù), 商品評論服務(wù). 如圖所示:

image

調(diào)用三個依賴服務(wù)會共享商品詳情服務(wù)的線程池. 如果其中的商品評論服務(wù)不可用, 就會出現(xiàn)線程池里所有線程都因等待響應(yīng)而被阻塞, 從而造成服務(wù)雪崩. 如圖所示:

image

Hystrix通過將每個依賴服務(wù)分配獨(dú)立的線程池進(jìn)行資源隔離, 從而避免服務(wù)雪崩.
如下圖所示, 當(dāng)商品評論服務(wù)不可用時, 即使商品服務(wù)獨(dú)立分配的20個線程全部處于同步等待狀態(tài),也不會影響其他依賴服務(wù)的調(diào)用.

image

熔斷器模式

熔斷器模式定義了熔斷器開關(guān)相互轉(zhuǎn)換的邏輯:

image

服務(wù)的健康狀況 = 請求失敗數(shù) / 請求總數(shù).
熔斷器開關(guān)由關(guān)閉到打開的狀態(tài)轉(zhuǎn)換是通過當(dāng)前服務(wù)健康狀況和設(shè)定閾值比較決定的.

  1. 當(dāng)熔斷器開關(guān)關(guān)閉時, 請求被允許通過熔斷器. 如果當(dāng)前健康狀況高于設(shè)定閾值, 開關(guān)繼續(xù)保持關(guān)閉. 如果當(dāng)前健康狀況低于設(shè)定閾值, 開關(guān)則切換為打開狀態(tài).

  2. 當(dāng)熔斷器開關(guān)打開時, 請求被禁止通過.

  3. 當(dāng)熔斷器開關(guān)處于打開狀態(tài), 經(jīng)過一段時間后, 熔斷器會自動進(jìn)入半開狀態(tài), 這時熔斷器只允許一個請求通過. 當(dāng)該請求調(diào)用成功時, 熔斷器恢復(fù)到關(guān)閉狀態(tài). 若該請求失敗, 熔斷器繼續(xù)保持打開狀態(tài), 接下來的請求被禁止通過.

熔斷器的開關(guān)能保證服務(wù)調(diào)用者在調(diào)用異常服務(wù)時, 快速返回結(jié)果, 避免大量的同步等待. 并且熔斷器能在一段時間后繼續(xù)偵測請求執(zhí)行結(jié)果, 提供恢復(fù)服務(wù)調(diào)用的可能.

命令模式

Hystrix使用命令模式(繼承HystrixCommand類)來包裹具體的服務(wù)調(diào)用邏輯(run方法), 并在命令模式中添加了服務(wù)調(diào)用失敗后的降級邏輯(getFallback).
同時我們在Command的構(gòu)造方法中可以定義當(dāng)前服務(wù)線程池和熔斷器的相關(guān)參數(shù). 如下代碼所示:

public class Service1HystrixCommand extends HystrixCommand<Response> {
  private Service1 service;
  private Request request;
 
  public Service1HystrixCommand(Service1 service, Request request){
    supper(
      Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ServiceGroup"))
          .andCommandKey(HystrixCommandKey.Factory.asKey("servcie1query"))
          .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("service1ThreadPool"))
          .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
            .withCoreSize(20))//服務(wù)線程池數(shù)量
          .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
            .withCircuitBreakerErrorThresholdPercentage(60)//熔斷器關(guān)閉到打開閾值
            .withCircuitBreakerSleepWindowInMilliseconds(3000)//熔斷器打開到關(guān)閉的時間窗長度
      ))
      this.service = service;
      this.request = request;
    );
  }
 
  @Override
  protected Response run(){
    return service1.call(request);
  }
 
  @Override
  protected Response getFallback(){
    return Response.dummy();
  }
}

在使用了Command模式構(gòu)建了服務(wù)對象之后, 服務(wù)便擁有了熔斷器和線程池的功能.
image

Hystrix的內(nèi)部處理邏輯

下圖為Hystrix服務(wù)調(diào)用的內(nèi)部邏輯:
image
  1. 構(gòu)建Hystrix的Command對象, 調(diào)用執(zhí)行方法.

  2. Hystrix檢查當(dāng)前服務(wù)的熔斷器開關(guān)是否開啟, 若開啟, 則執(zhí)行降級服務(wù)getFallback方法.

  3. 若熔斷器開關(guān)關(guān)閉, 則Hystrix檢查當(dāng)前服務(wù)的線程池是否能接收新的請求, 若超過線程池已滿, 則執(zhí)行降級服務(wù)getFallback方法.

  4. 若線程池接受請求, 則Hystrix開始執(zhí)行服務(wù)調(diào)用具體邏輯run方法.

  5. 若服務(wù)執(zhí)行失敗, 則執(zhí)行降級服務(wù)getFallback方法, 并將執(zhí)行結(jié)果上報Metrics更新服務(wù)健康狀況.

  6. 若服務(wù)執(zhí)行超時, 則執(zhí)行降級服務(wù)getFallback方法, 并將執(zhí)行結(jié)果上報Metrics更新服務(wù)健康狀況.

  7. 若服務(wù)執(zhí)行成功, 返回正常結(jié)果.

  8. 若服務(wù)降級方法getFallback執(zhí)行成功, 則返回降級結(jié)果.

  9. 若服務(wù)降級方法getFallback執(zhí)行失敗, 則拋出異常.

Hystrix Metrics的實(shí)現(xiàn)

Hystrix的Metrics中保存了當(dāng)前服務(wù)的健康狀況, 包括服務(wù)調(diào)用總次數(shù)和服務(wù)調(diào)用失敗次數(shù)等. 根據(jù)Metrics的計數(shù), 熔斷器從而能計算出當(dāng)前服務(wù)的調(diào)用失敗率, 用來和設(shè)定的閾值比較從而決定熔斷器的狀態(tài)切換邏輯. 因此Metrics的實(shí)現(xiàn)非常重要.

1.4之前的滑動窗口實(shí)現(xiàn)

Hystrix在這些版本中的使用自己定義的滑動窗口數(shù)據(jù)結(jié)構(gòu)來記錄當(dāng)前時間窗的各種事件(成功,失敗,超時,線程池拒絕等)的計數(shù).
事件產(chǎn)生時, 數(shù)據(jù)結(jié)構(gòu)根據(jù)當(dāng)前時間確定使用舊桶還是創(chuàng)建新桶來計數(shù), 并在桶中對計數(shù)器經(jīng)行修改.
這些修改是多線程并發(fā)執(zhí)行的, 代碼中有不少加鎖操作,邏輯較為復(fù)雜.

image

1.5之后的滑動窗口實(shí)現(xiàn)

Hystrix在這些版本中開始使用RxJava的Observable.window()實(shí)現(xiàn)滑動窗口.
RxJava的window使用后臺線程創(chuàng)建新桶, 避免了并發(fā)創(chuàng)建桶的問題.
同時RxJava的單線程無鎖特性也保證了計數(shù)變更時的線程安全. 從而使代碼更加簡潔.
以下為我使用RxJava的window方法實(shí)現(xiàn)的一個簡易滑動窗口Metrics, 短短幾行代碼便能完成統(tǒng)計功能,足以證明RxJava的強(qiáng)大:

@Test
public void timeWindowTest() throws Exception{
  Observable<Integer> source = Observable.interval(50, TimeUnit.MILLISECONDS).map(i -> RandomUtils.nextInt(2));
  source.window(1, TimeUnit.SECONDS).subscribe(window -> {
    int[] metrics = new int[2];
    window.subscribe(i -> metrics[i]++,
      InternalObservableUtils.ERROR_NOT_IMPLEMENTED,
      () -> System.out.println("窗口Metrics:" + JSON.toJSONString(metrics)));
  });
  TimeUnit.SECONDS.sleep(3);
}

總結(jié)
通過使用Hystrix,我們能方便的防止雪崩效應(yīng), 同時使系統(tǒng)具有自動降級和自動恢復(fù)服務(wù)的效果。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市腌零,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌糕殉,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡风罩,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門芯侥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泊交,“玉大人乳讥,你說我怎么就攤上這事±螅” “怎么了云石?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長研乒。 經(jīng)常有香客問我汹忠,道長,這世上最難降的妖魔是什么雹熬? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任宽菜,我火速辦了婚禮,結(jié)果婚禮上竿报,老公的妹妹穿的比我還像新娘铅乡。我一直安慰自己,他們只是感情好烈菌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布阵幸。 她就那樣靜靜地躺著,像睡著了一般芽世。 火紅的嫁衣襯著肌膚如雪挚赊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天济瓢,我揣著相機(jī)與錄音荠割,去河邊找鬼。 笑死旺矾,一個胖子當(dāng)著我的面吹牛蔑鹦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播宠漩,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼举反,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了扒吁?” 一聲冷哼從身側(cè)響起火鼻,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎雕崩,沒想到半個月后魁索,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡盼铁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年粗蔚,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饶火。...
    茶點(diǎn)故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡鹏控,死狀恐怖致扯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情当辐,我是刑警寧澤抖僵,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站缘揪,受9級特大地震影響耍群,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜找筝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一蹈垢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧袖裕,春花似錦曹抬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嚷闭。三九已至攒岛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胞锰,已是汗流浹背灾锯。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嗅榕,地道東北人顺饮。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像凌那,于是被迫代替她去往敵國和親兼雄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容