防雪崩利器:熔斷器 Hystrix 的原理與使用

前言

分佈式系統中常常會出現某個基礎服務不可用形成整個系統不可用的狀況, 這種現象被稱爲服務雪崩效應. 爲了應對服務雪崩, 一種常見的作法是手動服務降級. 而Hystrix的出現,給咱們提供了另外一種選擇.java

服務雪崩效應的定義

服務雪崩效應是一種因 服務提供者 的不可用致使 服務調用者 的不可用,並將不可用 逐漸放大 的過程.若是所示:後端

圖片描述

上圖中, A爲服務提供者, B爲A的服務調用者, C和D是B的服務調用者. 當A的不可用,引發B的不可用,並將不可用逐漸放大C和D時, 服務雪崩就造成了.緩存

服務雪崩效應造成的緣由

我把服務雪崩的參與者簡化爲 服務提供者服務調用者, 並將服務雪崩產生的過程分爲如下三個階段來分析造成的緣由:安全

  1. 服務提供者不可用服務器

  2. 重試加大流量網絡

  3. 服務調用者不可用數據結構

圖片描述

服務雪崩的每一個階段均可能由不一樣的緣由形成, 好比形成 服務不可用 的緣由有:多線程

  • 硬件故障併發

  • 程序Bugdom

  • 緩存擊穿

  • 用戶大量請求

硬件故障可能爲硬件損壞形成的服務器主機宕機, 網絡硬件故障形成的服務提供者的不可訪問.
緩存擊穿通常發生在緩存應用重啓, 全部緩存被清空時,以及短期內大量緩存失效時. 大量的緩存不命中, 使請求直擊後端,形成服務提供者超負荷運行,引發服務不可用.
在秒殺和大促開始前,若是準備不充分,用戶發起大量請求也會形成服務提供者的不可用.

而造成 重試加大流量 的緣由有:

  • 用戶重試

  • 代碼邏輯重試

在服務提供者不可用後, 用戶因爲忍受不了界面上長時間的等待,而不斷刷新頁面甚至提交表單.
服務調用端的會存在大量服務異常後的重試邏輯.
這些重試都會進一步加大請求流量.

最後, 服務調用者不可用 產生的主要緣由是:

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

當服務調用者使用 同步調用 時, 會產生大量的等待線程佔用系統資源. 一旦線程資源被耗盡,服務調用者提供的服務也將處於不可用狀態, 因而服務雪崩效應產生了.

服務雪崩的應對策略

針對形成服務雪崩的不一樣緣由, 可使用不一樣的應對策略:

  1. 流量控制

  2. 改進緩存模式

  3. 服務自動擴容

  4. 服務調用者降級服務

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

  • 網關限流

  • 用戶交互限流

  • 關閉重試

由於Nginx的高性能, 目前一線互聯網公司大量採用Nginx+Lua的網關進行流量控制, 由此而來的OpenResty也愈來愈熱門.

用戶交互限流的具體措施有: 1. 採用加載動畫,提升用戶的忍耐等待時間. 2. 提交按鈕添增強制等待時間機制.

改進緩存模式 的措施包括:

  • 緩存預加載

  • 同步改成異步刷新

服務自動擴容 的措施主要有:

  • AWS的auto scaling

服務調用者降級服務 的措施包括:

  • 資源隔離

  • 對依賴服務進行分類

  • 不可用服務的調用快速失敗

資源隔離主要是對調用服務的線程池進行隔離.

咱們根據具體業務,將依賴服務分爲: 強依賴和若依賴. 強依賴服務不可用會致使當前業務停止,而弱依賴服務的不可用不會致使當前業務的停止.

不可用服務的調用快速失敗通常經過 超時機制, 熔斷器 和熔斷後的 降級方法 來實現.

使用Hystrix預防服務雪崩

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

Hystrix的設計原則包括:

  • 資源隔離

  • 熔斷器

  • 命令模式

資源隔離

貨船爲了進行防止漏水和火災的擴散,會將貨倉分隔爲多個, 以下圖所示:

圖片描述

這種資源隔離減小風險的方式被稱爲:Bulkheads(艙壁隔離模式).
Hystrix將一樣的模式運用到了服務調用者上.

在一個高度服務化的系統中,咱們實現的一個業務邏輯一般會依賴多個服務,好比:
商品詳情展現服務會依賴商品服務, 價格服務, 商品評論服務. 如圖所示:

圖片描述

調用三個依賴服務會共享商品詳情服務的線程池. 若是其中的商品評論服務不可用, 就會出現線程池裏全部線程都因等待響應而被阻塞, 從而形成服務雪崩. 如圖所示:

圖片描述

Hystrix經過將每一個依賴服務分配獨立的線程池進行資源隔離, 從而避免服務雪崩.
以下圖所示, 當商品評論服務不可用時, 即便商品服務獨立分配的20個線程所有處於同步等待狀態,也不會影響其餘依賴服務的調用.

圖片描述

熔斷器模式

熔斷器模式定義了熔斷器開關相互轉換的邏輯:

圖片描述

服務的健康情況 = 請求失敗數 / 請求總數.
熔斷器開關由關閉到打開的狀態轉換是經過當前服務健康情況和設定閾值比較決定的.

  1. 當熔斷器開關關閉時, 請求被容許經過熔斷器. 若是當前健康情況高於設定閾值, 開關繼續保持關閉. 若是當前健康情況低於設定閾值, 開關則切換爲打開狀態.

  2. 當熔斷器開關打開時, 請求被禁止經過.

  3. 當熔斷器開關處於打開狀態, 通過一段時間後, 熔斷器會自動進入半開狀態, 這時熔斷器只容許一個請求經過. 當該請求調用成功時, 熔斷器恢復到關閉狀態. 若該請求失敗, 熔斷器繼續保持打開狀態, 接下來的請求被禁止經過.

熔斷器的開關能保證服務調用者在調用異常服務時, 快速返回結果, 避免大量的同步等待. 而且熔斷器能在一段時間後繼續偵測請求執行結果, 提供恢復服務調用的可能.

命令模式

Hystrix使用命令模式(繼承HystrixCommand類)來包裹具體的服務調用邏輯(run方法), 並在命令模式中添加了服務調用失敗後的降級邏輯(getFallback).
同時咱們在Command的構造方法中能夠定義當前服務線程池和熔斷器的相關參數. 以下代碼所示:

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))//服務線程池數量
          .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
            .withCircuitBreakerErrorThresholdPercentage(60)//熔斷器關閉到打開閾值
            .withCircuitBreakerSleepWindowInMilliseconds(3000)//熔斷器打開到關閉的時間窗長度
      ))
      this.service = service;
      this.request = request;
    );
  }

  @Override
  protected Response run(){
    return service1.call(request);
  }

  @Override
  protected Response getFallback(){
    return Response.dummy();
  }
}

在使用了Command模式構建了服務對象以後, 服務便擁有了熔斷器和線程池的功能.
圖片描述

Hystrix的內部處理邏輯

下圖爲Hystrix服務調用的內部邏輯:
圖片描述

  1. 構建Hystrix的Command對象, 調用執行方法.

  2. Hystrix檢查當前服務的熔斷器開關是否開啓, 若開啓, 則執行降級服務getFallback方法.

  3. 若熔斷器開關關閉, 則Hystrix檢查當前服務的線程池是否能接收新的請求, 若超過線程池已滿, 則執行降級服務getFallback方法.

  4. 若線程池接受請求, 則Hystrix開始執行服務調用具體邏輯run方法.

  5. 若服務執行失敗, 則執行降級服務getFallback方法, 並將執行結果上報Metrics更新服務健康情況.

  6. 若服務執行超時, 則執行降級服務getFallback方法, 並將執行結果上報Metrics更新服務健康情況.

  7. 若服務執行成功, 返回正常結果.

  8. 若服務降級方法getFallback執行成功, 則返回降級結果.

  9. 若服務降級方法getFallback執行失敗, 則拋出異常.

Hystrix Metrics的實現

Hystrix的Metrics中保存了當前服務的健康情況, 包括服務調用總次數和服務調用失敗次數等. 根據Metrics的計數, 熔斷器從而能計算出當前服務的調用失敗率, 用來和設定的閾值比較從而決定熔斷器的狀態切換邏輯. 所以Metrics的實現很是重要.

1.4以前的滑動窗口實現

Hystrix在這些版本中的使用本身定義的滑動窗口數據結構來記錄當前時間窗的各類事件(成功,失敗,超時,線程池拒絕等)的計數.
事件產生時, 數據結構根據當前時間肯定使用舊桶仍是建立新桶來計數, 並在桶中對計數器經行修改.
這些修改是多線程併發執行的, 代碼中有很多加鎖操做,邏輯較爲複雜.

圖片描述

1.5以後的滑動窗口實現

Hystrix在這些版本中開始使用RxJava的Observable.window()實現滑動窗口.
RxJava的window使用後臺線程建立新桶, 避免了併發建立桶的問題.
同時RxJava的單線程無鎖特性也保證了計數變動時的線程安全. 從而使代碼更加簡潔.
如下爲我使用RxJava的window方法實現的一個簡易滑動窗口Metrics, 短短几行代碼便能完成統計功能,足以證實RxJava的強大:

@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);
}

總結

經過使用Hystrix,咱們能方便的防止雪崩效應, 同時使系統具備自動降級和自動恢復服務的效果.

相關文章
相關標籤/搜索