熔斷器 Hystrix 源碼解析 —— 請求執行(四)之失敗回退邏輯

https://www.javazhiyin.com/15257.html

https://blog.csdn.net/chenpeng19910926/article/details/76269339

https://zhenbianshu.github.io/2018/09/hystrix_configuration_analysis.html

1. 概述

本文主要分享 Hystrix 命令執行(四)之失敗回退邏輯

建議 :對 RxJava 已經有一定的瞭解的基礎上閱讀本文。

Hystrix 執行命令整體流程如下圖:

FROM 《【翻譯】Hystrix文檔-實現原理》「流程圖」
熔斷器 Hystrix 源碼解析 —— 請求執行(四)之失敗回退邏輯

  • 圈 :Hystrix 命令執行失敗,執行回退邏輯。也就是大家經常在文章中看到的“服務降級”

  • 圈 :四種情況會觸發失敗回退邏輯( fallback )。

    • 第一種 : short-circuit ,處理鏈路處於熔斷的回退邏輯,在 「3. #handleShortCircuitViaFallback()」 詳細解析。

    • 第二種 : semaphore-rejection ,處理信號量獲得失敗的回退邏輯,在 「4. #handleShortCircuitViaFallback()」 詳細解析。

    • 第三種 : thread-pool-rejection ,處理線程池提交任務拒絕的回退邏輯,在 「5. #handleThreadPoolRejectionViaFallback()」 詳細解析。

    • 第四種 : execution-timeout ,處理命令執行超時的回退邏輯,在 「6. #handleTimeoutViaFallback()」 詳細解析。

    • 第五種 : execution-failure ,處理命令執行異常的回退邏輯,在 「7. #handleFailureViaFallback()」 詳細解析。

    • 第六種 : bad-request ,TODO 【2014】【HystrixBadRequestException】,和 hystrix-javanica 子項目相關。

另外, #handleXXXX() 方法,整體代碼比較類似,最終都是調用 #getFallbackOrThrowException() 方法,獲得【回退邏輯 Observable】或者【異常 Observable】,在 「8. #getFallbackOrThrowException(...)」 詳細解析。

 

NOTICE:

  1. 需要hystrix感知(需要執行fallback且記入CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE統計)的異常都需要拋出來,否則如果所有的異常都catch住,發生異常時,hystrix無法感知,無法統計錯誤率。需要Hystrix忽略的異常,增加 @HystrixCommand(ignoreExceptions = {BadRequestException.class}即可,配置ignoreExceptions的異常不會統計到錯誤率裏面。

  2. groupKey與CommandKey無直接關係,我們可以簡單的理解爲,groupKey控制是否同一個線程池,commandKey控制是否同一個熔斷器。

  3. 需要增加hystrix註解的方法必須爲public,fallback方法可以爲private。兩者需要返回值和參數相同。

  4. 在類本身的其他方法中調用增加hystrix註解的方法,無法達到自動降級目的

  5. 在fallback方法中不允許有遠程方法調用,方法儘量要輕,調用其他外部接口也要進行hystrix降級。否則執行fallback方法會拋出異常。

Hystrix文檔:https://github.com/Netflix/Hystrix/wiki

Hystrix-javanica文檔:https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-javanica

Hystrix配置文檔:https://github.com/Netflix/Hystrix/wiki/configuration

 

前言


不久前在部門週會上分享了 Hystrix 源碼解析之後,就無奈地背上了”專家包袱”,同事們都認爲我對 Hystrix 很熟,我們接觸 Hystrix 更多的還是工作中的使用和配置,所以很多人一遇到 Hystrix 的配置問題就會過來問我。爲了不讓他們失望,我把 Hystrix 的配置文檔 仔細看了一遍,將有疑問的點通過翻源碼、查官方 issue、自己實驗的方式整理了一遍,這纔對 Hystrix 的配置有了一定的瞭解。

在瞭解這些配置項的過程中,我也發現了很多坑,平常我們使用中認爲理所應當的值並不會讓 Hystrix 如期望工作,沒有經過斟酌就複製粘貼的配置會讓 Hystrix 永遠不會起作用。於是寫下本文,希望能幫助小夥伴們掌握 Hystrix。如果想了解 Hystrix 的話,可以搭配我之前的分享 PPT:Hystrix 源碼解析

轉載隨意,文章會持續修訂,請註明來源地址:https://zhenbianshu.github.io 。

HystrixCommand


配置方式

我們的配置都是基於 HystrixCommand 的,我們通過在方法上添加 @HystrixCommand 註解並配置註解的參數來實現配置,但有的時候一個類裏面會有多個 Hystrix 方法,每個方法都是類似配置的話會冗餘很多代碼,這時候我們可以在類上使用 @DefaultProperties 註解來給整個類的 Hystrix 方法設置一個默認值。

配置項

下面是 HystrixCommand 支持的參數,除了 commandKey/observableExecutionMode/fallbackMethod 外,都可以使用 @DefaultProperties 配置默認值。

  • commandKey:用來標識一個 Hystrix 命令,默認會取被註解的方法名。需要注意:Hystrix 裏同一個鍵的唯一標識並不包括 groupKey,建議取一個獨一二無的名字,防止多個方法之間因爲鍵重複而互相影響。

  • groupKey:一組 Hystrix 命令的集合, 用來統計、報告,默認取類名,可不配置。

  • threadPoolKey:用來標識一個線程池,如果沒設置的話會取 groupKey,很多情況下都是同一個類內的方法在共用同一個線程池,如果兩個共用同一線程池的方法上配置了同樣的屬性,在第一個方法被執行後線程池的屬性就固定了,所以屬性會以第一個被執行的方法上的配置爲準。

  • commandProperties:與此命令相關的屬性。

  • threadPoolProperties:與線程池相關的屬性,

  • observableExecutionMode:當 Hystrix 命令被包裝成 RxJava 的 Observer 異步執行時,此配置指定了 Observable 被執行的模式,默認是 ObservableExecutionMode.EAGER,Observable 會在被創建後立刻執行,而 ObservableExecutionMode.EAGER模式下,則會產生一個 Observable 被 subscribe 後執行。我們常見的命令都是同步執行的,此配置項可以不配置。

  • ignoreExceptions:默認 Hystrix 在執行方法時捕獲到異常時執行回退,並統計失敗率以修改熔斷器的狀態,而被忽略的異常則會直接拋到外層,不會執行回退方法,也不會影響熔斷器的狀態。

  • raiseHystrixExceptions:當配置項包括 HystrixRuntimeException 時,所有的未被忽略的異常都會被包裝成 HystrixRuntimeException,配置其他種類的異常好像並沒有什麼影響。

  • fallbackMethod:方法執行時熔斷、錯誤、超時時會執行的回退方法,需要保持此方法與 Hystrix 方法的簽名和返回值一致。

  • defaultFallback:默認回退方法,當配置 fallbackMethod 項時此項沒有意義,另外,默認回退方法不能有參數,返回值要與 Hystrix方法的返回值相同。

commandProperties


配置方式

Hystrix 的命令屬性是由 @HystrixProperty 註解數組構成的,HystrixProperty 由 name 和 value 兩個屬性,數據類型都是字符串。

以下將所有的命令屬性分組來介紹。

線程隔離(Isolation)

  • execution.isolation.strategy: 配置請求隔離的方式,有 threadPool(線程池,默認)和 semaphore(信號量)兩種,信號量方式高效但配置不靈活,我們一般採用 Java 裏常用的線程池方式。

  • execution.timeout.enabled:是否給方法執行設置超時,默認爲 true。

  • execution.isolation.thread.timeoutInMilliseconds:方法執行超時時間,默認值是 1000,即 1秒,此值根據業務場景配置。

  • execution.isolation.thread.interruptOnTimeoutexecution.isolation.thread.interruptOnCancel:是否在方法執行超時/被取消時中斷方法。需要注意在 JVM 中我們無法強制中斷一個線程,如果 Hystrix 方法裏沒有處理中斷信號的邏輯,那麼中斷會被忽略。

  • execution.isolation.semaphore.maxConcurrentRequests:默認值是 10,此配置項要在 execution.isolation.strategy 配置爲 semaphore 時纔會生效,它指定了一個 Hystrix 方法使用信號量隔離時的最大併發數,超過此併發數的請求會被拒絕。信號量隔離的配置就這麼一個,也是前文說信號量隔離配置不靈活的原因。

統計器(Metrics)

滑動窗口: Hystrix 的統計器是由滑動窗口來實現的,我們可以這麼來理解滑動窗口:一位乘客坐在正在行駛的列車的靠窗座位上,列車行駛的公路兩側種着一排挺拔的白楊樹,隨着列車的前進,路邊的白楊樹迅速從窗口滑過,我們用每棵樹來代表一個請求,用列車的行駛代表時間的流逝,那麼,列車上的這個窗口就是一個典型的滑動窗口,這個乘客能通過窗口看到的白楊樹就是 Hystrix 要統計的數據。

: bucket 是 Hystrix 統計滑動窗口數據時的最小單位。同樣類比列車窗口,在列車速度非常快時,如果每掠過一棵樹就統計一次窗口內樹的數據,顯然開銷非常大,如果乘客將窗口分成十分,列車前進行時每掠過窗口的十分之一就統計一次數據,開銷就完全可以接受了。 Hystrix 的 bucket (桶)也就是窗口 N分之一 的概念。

  • metrics.rollingStats.timeInMilliseconds:此配置項指定了窗口的大小,單位是 ms,默認值是 1000,即一個滑動窗口默認統計的是 1s 內的請求數據。

  • metrics.healthSnapshot.intervalInMilliseconds:它指定了健康數據統計器(影響 Hystrix 熔斷)中每個桶的大小,默認是 500ms,在進行統計時,Hystrix 通過 metrics.rollingStats.timeInMilliseconds / metrics.healthSnapshot.intervalInMilliseconds 計算出桶數,在窗口滑動時,每滑過一個桶的時間間隔時就統計一次當前窗口內請求的失敗率。

  • metrics.rollingStats.numBuckets:Hystrix 會將命令執行的結果類型都統計彙總到一塊,給上層應用使用或生成統計圖表,此配置項即指定了,生成統計數據流時滑動窗口應該拆分的桶數。此配置項最易跟上面的 metrics.healthSnapshot.intervalInMilliseconds 搞混,認爲此項影響健康數據流的桶數。 此項默認是 10,並且需要保持此值能被 metrics.rollingStats.timeInMilliseconds 整除。

  • metrics.rollingPercentile.enabled:是否統計方法響應時間百分比,默認爲 true 時,Hystrix 會統計方法執行的 1%,10%,50%,90%,99% 等比例請求的平均耗時用以生成統計圖表。

  • metrics.rollingPercentile.timeInMilliseconds:統計響應時間百分比時的窗口大小,默認爲 60000,即一分鐘。

  • metrics.rollingPercentile.numBuckets:統計響應時間百分比時滑動窗口要劃分的桶用,默認爲6,需要保持能被metrics.rollingPercentile.timeInMilliseconds 整除。

  • metrics.rollingPercentile.bucketSize:統計響應時間百分比時,每個滑動窗口的桶內要保留的請求數,桶內的請求超出這個值後,會覆蓋最前面保存的數據。默認值爲 100,在統計響應百分比配置全爲默認的情況下,每個桶的時間長度爲 10s = 60000ms / 6,但這 10s 內只保留最近的 100 條請求的數據。

熔斷器(Circuit Breaker)

  • circuitBreaker.enabled:是否啓用熔斷器,默認爲 true;

  • circuitBreaker.forceOpen: circuitBreaker.forceClosed:是否強制啓用/關閉熔斷器,強制啓用關閉都想不到什麼應用的場景,保持默認值,不配置即可。

  • circuitBreaker.requestVolumeThreshold:啓用熔斷器功能窗口時間內的最小請求數。試想如果沒有這麼一個限制,我們配置了 50% 的請求失敗會打開熔斷器,窗口時間內只有 3 條請求,恰巧兩條都失敗了,那麼熔斷器就被打開了,5s 內的請求都被快速失敗。此配置項的值需要根據接口的 QPS 進行計算,值太小會有誤打開熔斷器的可能,值太大超出了時間窗口內的總請求數,則熔斷永遠也不會被觸發。建議設置爲 QPS * 窗口秒數 * 60%

  • circuitBreaker.errorThresholdPercentage:在通過滑動窗口獲取到當前時間段內 Hystrix 方法執行的失敗率後,就需要根據此配置來判斷是否要將熔斷器打開了。 此配置項默認值是 50,即窗口時間內超過 50% 的請求失敗後會打開熔斷器將後續請求快速失敗。

  • circuitBreaker.sleepWindowInMilliseconds:熔斷器打開後,所有的請求都會快速失敗,但何時服務恢復正常就是下一個要面對的問題。熔斷器打開時,Hystrix 會在經過一段時間後就放行一條請求,如果這條請求執行成功了,說明此時服務很可能已經恢復了正常,那麼會將熔斷器關閉,如果此請求執行失敗,則認爲服務依然不可用,熔斷器繼續保持打開狀態。此配置項指定了熔斷器打開後經過多長時間允許一次請求嘗試執行,默認值是 5000。

其他(Context/Fallback)

  • requestCache.enabled:是否啓用請求結果緩存。默認是 true,但它並不意味着我們的每個請求都會被緩存。緩存請求結果和從緩存中獲取結果都需要我們配置 cacheKey,並且在方法上使用 @CacheResult 註解聲明一個緩存上下文。

  • requestLog.enabled:是否啓用請求日誌,默認爲 true。

  • fallback.enabled:是否啓用方法回退,默認爲 true 即可。

  • fallback.isolation.semaphore.maxConcurrentRequests:回退方法執行時的最大併發數,默認是10,如果大量請求的回退方法被執行時,超出此併發數的請求會拋出 REJECTED_SEMAPHORE_FALLBACK 異常。

threadPoolProperties


配置方式

線程池的配置也是由 HystrixProperty 數組構成,配置方式與命令屬性一致。

配置項

  • coreSize:核心線程池的大小,默認值是 10,一般根據 QPS * 99% cost + redundancy count 計算得出。

  • allowMaximumSizeToDivergeFromCoreSize:是否允許線程池擴展到最大線程池數量,默認爲 false;

  • maximumSize:線程池中線程的最大數量,默認值是 10,此配置項單獨配置時並不會生效,需要啓用 allowMaximumSizeToDivergeFromCoreSize 項。

  • maxQueueSize:作業隊列的最大值,默認值爲 -1,設置爲此值時,隊列會使用 SynchronousQueue,此時其 size 爲0,Hystrix 不會向隊列內存放作業。如果此值設置爲一個正的 int 型,隊列會使用一個固定 size 的 LinkedBlockingQueue,此時在覈心線程池內的線程都在忙碌時,會將作業暫時存放在此隊列內,但超出此隊列的請求依然會被拒絕。

  • queueSizeRejectionThreshold:由於 maxQueueSize 值在線程池被創建後就固定了大小,如果需要動態修改隊列長度的話可以設置此值,即使隊列未滿,隊列內作業達到此值時同樣會拒絕請求。此值默認是 5,所以有時候只設置了 maxQueueSize 也不會起作用。

  • keepAliveTimeMinutes:由上面的 maximumSize,我們知道,線程池內核心線程數目都在忙碌,再有新的請求到達時,線程池容量可以被擴充爲到最大數量,等到線程池空閒後,多於核心數量的線程還會被回收,此值指定了線程被回收前的存活時間,默認爲 2,即兩分鐘。

工作方式

Hystrix 內線程池的使用是基於 Java 內置線程池的簡單包裝,通常有以下三種狀態:

  • 如果請求量少,達不到 coreSize,通常會使用核心線程來執行任務。
  • 如果設置了 maxQueueSize,當請求數超過了 coreSize, 通常會把請求放到 queue 裏,待覈心線程有空閒時消費。
  • 如果 queue 長度無法存儲請求,則會創建新線程執行直到達到 maximumSize 最大線程數,多出核心線程數的線程會在空閒時回收。

 

package com.example.demo.api;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager;
import java.util.Date;
import org.springframework.stereotype.Service;

@Service
public class BClient {

    @HystrixCommand(commandKey = "tripSettleGetConfig", groupKey = "tripSettleGetConfig", threadPoolKey = "tripSettleGetConfig",
            fallbackMethod = "hystrixServiceFallback",
            commandProperties = {
                    @HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "100")})
    public boolean hystrixService(){
        System.out.println(new Date() + " hystrixService");
//        int i = 10 / (1 - 1);
        try {
//            int i = 10 / (1 - 1);
            Thread.sleep(2000);
        } catch (Exception e) {
            System.out.println(new Date() + " hystrixService exception: " + e.getMessage());
//			int i = 10/(1-1);
        }
        System.out.println(new Date() + " hystrixService done 0");
        try {
            Thread.sleep(2000);
        } catch (Exception e) {
            System.out.println(new Date() + " hystrixService exception: " + e.getMessage());
        }
        System.out.println(new Date() + " hystrixService done 1");
        int i = 10/(1-1);
        System.out.println(new Date() + " hystrixService done 2");
        return true;
    }

    public boolean hystrixServiceFallback() {
        System.out.println(new Date() + " hystrixServiceFallback");
        return false;
    }

}



--------------------
執行結果:
1、exception可以觸發fallback
2、超時等fallback會中斷HystrixCommand中的線程
3、HystrixCommand中的線程被中斷後,如果捕獲了異常,會繼續往下執行
4、繼續往下執行時產生的exception不會再次觸發fallback,fallback只會執行一次

Tue Sep 03 14:10:17 CST 2019 hystrixService
Tue Sep 03 14:10:17 CST 2019 hystrixService exception: sleep interrupted
Tue Sep 03 14:10:17 CST 2019 hystrixService done 0
Tue Sep 03 14:10:17 CST 2019 hystrixServiceFallback
Tue Sep 03 14:10:19 CST 2019 hystrixService done 1

1、exception可以觸發fallback
2、超時等fallback會中斷HystrixCommand中的線程
3、HystrixCommand中的線程被中斷後,如果捕獲了異常,會繼續往下執行
4、繼續往下執行時產生的exception不會再次觸發fallback,fallback只會執行一次

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章