天天看點

【你好Hystrix】五:Hystrix斷路器子產品代碼解析-HystrixCircuitBreaker前言斷路器HystrixCircuitBreaker總結

目錄

  • 前言
  • 斷路器
  • HystrixCircuitBreaker
    • HystrixCircuitBreakerImpl
      • 屬性
      • 熔斷器打開
      • markSuccess
      • markNonSuccess
      • allowRequest
      • 嘗試執行-attemptExecution
  • 總結

前言

我們前面對

Hystrix

的名額統計方式做了詳細的介紹。

1.5.0

版本之前使用的是

HystrixRollingNumber

環形數組來作為名額的收集的子產品,而

1.5.0

版本之後使用的是更靈活的響應式收集模式。有了各項請求的名額那麼這一篇文章我們就來看看

Hystrix

的斷路器是如何使用這些名額來作為依據的。

斷路器

Hystrix的斷路器有三種狀态:打開、半開、關閉。

  • 關閉狀态:請求正常進入
  • 打開狀态:拒絕所有的請求,如果這時有降級的邏輯走降級邏輯
  • 半開狀态:打開狀态的預設過5s狀态會自動變成半開狀态,該狀态下允許一個請求進入,如果請求成功關閉斷路器 如果請求失敗重新進入打開狀态。

如下借用官網的一張圖:

【你好Hystrix】五:Hystrix斷路器子產品代碼解析-HystrixCircuitBreaker前言斷路器HystrixCircuitBreaker總結

我們下面主要是來看Hystrix是如何實作這三種狀态的轉換的。

HystrixCircuitBreaker

該接口定義了如下方法:

/**
     * 每次請求都會判斷是否允許請求 該方法具有幂等性
     */
    boolean allowRequest();

    /**
     * 斷路器是否處于打開的狀态
     */
    boolean isOpen();

    /**
     * 标記請求成功
     */
    void markSuccess();

    /**
     * 标記請求失敗
     */
    void markNonSuccess();

    /**
     * 嘗試執行
     */
    boolean attemptExecution();
           

上面方法都很直接 并且我們可以認為

HystrixCircuitBreaker

有且僅有一個實作

HystrixCircuitBreakerImpl

(

NoOpCircuitBreaker

也是一種實作 但是沒有太大意義)

HystrixCircuitBreakerImpl

作為

HystrixCircuitBreaker

的唯一的實作

HystrixCircuitBreakerImpl

重要程度不言而喻。

(注意看源碼的注釋)

屬性

//配置
private final HystrixCommandProperties properties;
//名額統計器  這個我們前面沒有說到 但是你可以簡單的了解它是一個聚合(對各種統計流的聚合)
 //特别是聚合了HealthCountsStream 
 private final HystrixCommandMetrics metrics;
 //斷路器的三種狀态
 enum Status {
     CLOSED, OPEN, HALF_OPEN;
 }
 //儲存目前斷路器的狀态預設是關閉的
 private final AtomicReference<Status> status = new AtomicReference<Status>(Status.CLOSED);
 //熔斷器關閉的記錄為-1
 private final AtomicLong circuitOpened = new AtomicLong(-1);
 //儲存對HealthCountsStream的訂閱結果 用于重訂閱。那可能會問 為什麼要重訂閱
 //其實就是為了初始化統計資料 例如從打開狀态的進入關閉狀态 這個時候需要重新統計名額來為下一次判定做準備
 private final AtomicReference<Subscription> activeSubscription = new AtomicReference<Subscription>(null);
           

熔斷器打開

//這個方法很重要 通過訂閱HealthCountsStream來實時判斷是否要打開熔斷開關
  private Subscription subscribeToStream() {
       return metrics.getHealthCountsStream()
               .observe()
               .subscribe(new Subscriber<HealthCounts>() {
                   @Override
                   public void onCompleted() {
                   }
                   @Override
                   public void onError(Throwable e) {
                   }
                   @Override
                   public void onNext(HealthCounts hc) {
                   //判斷目前的請求總量是否超過我們設定的請求量的門檻值
                   //可以通過hystrix.command.default.circuitBreaker.requestVolumeThreshold來配置請求門檻值
                   //預設是 20個
                       if (hc.getTotalRequests() < properties.circuitBreakerRequestVolumeThreshold().get()) {
                       } else {
                   //如果失敗率大于我們配置的失敗率 就把熔斷狀态改為打開的狀态并記錄打開的時間
                   //失敗率預設是50% 10s 20個請求 50%都失敗了 才會打開熔斷的開關
                           if (hc.getErrorPercentage() < properties.circuitBreakerErrorThresholdPercentage().get()) {
                           } else {
                               if (status.compareAndSet(Status.CLOSED, Status.OPEN)) {                               circuitOpened.set(System.currentTimeMillis());
                               }
                           }
                       }
                   }
               });
   }
           

上面的方法主要是一個訂閱的邏輯 然後根據訂閱的資料來判定是否需要打開 斷路器

  • 先判斷10s内的總請求數量是否大于

    預設值20

    可通過

    hystrix.command.default.circuitBreaker.requestVolumeThreshold

    來配置
  • 其次判斷失敗率是否超過

    預設值 50%

    可通過

    hystrix.command.default.circuitBreaker.errorThresholdPercentage

    來配置
  • 如果上面條件都滿足就打開斷路器 并記錄時間

markSuccess

@Override
 public void markSuccess() {
      //通過CAS關閉斷路器
     if (status.compareAndSet(Status.HALF_OPEN, Status.CLOSED)) {
         //重置統計流
         metrics.resetStream();
         //如果目前activeSubscription也有訂閱 那麼重新訂閱
         Subscription previousSubscription = activeSubscription.get();
         if (previousSubscription != null) {
             previousSubscription.unsubscribe();
         }
         Subscription newSubscription = subscribeToStream();
         activeSubscription.set(newSubscription);
         circuitOpened.set(-1L);
     }
 }
           

markNonSuccess

@Override
 public void markNonSuccess() {
     if (status.compareAndSet(Status.HALF_OPEN, Status.OPEN)) {
         circuitOpened.set(System.currentTimeMillis());
     }
 }
           

标記不成功 将斷路器從半開狀态變為打開狀态。

circuitOpened

記錄打開的時間戳

allowRequest

//isAfterSleepWindow 這個方法的邏輯主要判斷在斷路器打開的狀态下 預設5s 要嘗試去發送一筆請求
//判斷打開的時間是否超過5s
 @Override
 private boolean isAfterSleepWindow() {
   //circuitOpened 這個變量 在斷路器打開的狀态下存儲的是時間戳 
   final long circuitOpenTime = circuitOpened.get();
    final long currentTime = System.currentTimeMillis();
    //可以通過hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds來配置
    final long sleepWindowTime = properties.circuitBreakerSleepWindowInMilliseconds().get();
    return currentTime > circuitOpenTime + sleepWindowTime;
}

public boolean allowRequest() {
 //判斷是否配置斷路器強制打開
  if (properties.circuitBreakerForceOpen().get()) {
      return false;
  }
  //判斷是否配置斷路器強制關閉
  if (properties.circuitBreakerForceClosed().get()) {
      return true;
  }
  //如果斷路器是關閉的狀态 傳回true
  if (circuitOpened.get() == -1) {
      return true;
  } else {
     //進到這裡 說明斷路器是打開的狀态(可能是半開 可能是打開)
      //如果斷路器是半開的狀态傳回false
      if (status.get().equals(Status.HALF_OPEN)) {
          return false;
      } else {
          //如果是打開的狀态 要檢查目前狀态停留的時間是否超過了門檻值
          return isAfterSleepWindow();
      }
  }
}    
           

嘗試執行-attemptExecution

@Override
public boolean attemptExecution() {	
   if (properties.circuitBreakerForceOpen().get()) {
       return false;
   }
   if (properties.circuitBreakerForceClosed().get()) {
       return true;
   }
   if (circuitOpened.get() == -1) {
       return true;
   } else {
       //如果斷路器處于打開的狀态 并且如果打開狀态超過門檻值 就将狀态設定成半開狀态 
       //設定成功之後 true 讓目前請求可以執行
       if (isAfterSleepWindow()) {
           if (status.compareAndSet(Status.OPEN, Status.HALF_OPEN)) {
               return true;
           } else {
               return false;
           }
       } else {
           return false;
       }
   }
}
           

上面的代碼設定

if (status.compareAndSet(Status.OPEN, Status.HALF_OPEN))

這個分支很關鍵。将打開狀态下的斷路器設定成半打開狀态 設定成功之後立馬傳回true 允許一筆請求通過。設定失敗說明目前狀态是半開狀态,是以保證了隻有一筆請求能通過。

總結

斷路器的所有的邏輯都是在

HystrixCircuitBreakerImpl

中,是以它是斷路器的核心。幸運的是它的實作并不複雜 了解名額擷取這一塊是關鍵。

【你好Hystrix】五:Hystrix斷路器子產品代碼解析-HystrixCircuitBreaker前言斷路器HystrixCircuitBreaker總結

繼續閱讀