[享學Netflix] 三十二、Hystrix拋出HystrixBadRequestException異常爲何不會觸發熔斷?

我學習,我驕傲,我爲國家省口罩。

–> 返回專欄總目錄 <–
代碼下載地址:https://github.com/f641385712/netflix-learning

前言

通過前面文章我們知道了,Hystrix是個強大的熔斷降級框架:收集目標方法的成功、失敗等指標信息,觸發熔斷器。其中失敗信息通過異常來表示,交給Hystrix進行統計。

但是,有的時候有些異常是並不能觸發熔斷的,比如請求參數異常等,那怎麼辦呢?或許你已經知道了結論:目標方法執行拋出異常時,HystrixBadRequestException之外,其他異常都會認爲是Hystrix命令執行失敗並觸發服務降級處理邏輯。那麼本文將深入研究爲何如此,以及給出實踐方案。

說明:閱讀本文之前建議你已經瞭解了Hystrix的回退機制,如上篇文章:三十一、Hystrix觸發fallback降級邏輯的5種情況及代碼示例


正文

通過上篇文章,我們已經瞭解到了Hystrix觸發fallback降級邏輯的5種情況,也就是:

  1. short-circuited短路
  2. threadpool-rejected線程池拒絕
  3. semaphore-rejected信號量拒絕
  4. timed-out超時
  5. failed執行失敗

出現這些類型的失敗均會觸發Hystrix的fallback機制。本文將介紹HystrixBadRequestException這類型的異常將不會觸發fallabck機制。


認識HystrixBadRequestException

這個類本身並沒有什麼好說的,就是一個非常簡單的運行時異常:

public class HystrixBadRequestException extends RuntimeException {
    private static final long serialVersionUID = -8341452103561805856L;
    public HystrixBadRequestException(String message) {
        super(message);
    }
    public HystrixBadRequestException(String message, Throwable cause) {
        super(message, cause);
    }
}

不過它的Javadoc對該類的作用描述:用提供的參數或狀態表示錯誤而不是執行失敗的異常。與HystrixCommand拋出的所有其他異常不同,這不會觸發回退不會計算故障指標,因此不會觸發斷路器。

注意:當一個錯誤是由於用戶輸入IllegalArgumentException引起時(比如手誤),這個只應該使用,否則就會破壞容錯和回退行爲的目的。總的來說千萬別盲目使用,使用得最多的case是:結合Feign錯誤編碼器一起解決客戶端400異常而意外熔斷的問題~


熔斷器的數據從哪兒收集?

在解釋爲何不會觸發熔斷器之前,首先需要明白熔斷器的數據是從哪兒收集的?數據發射的源頭是哪兒?

在詳解HystrixCircuitBreaker這篇文章的時候,我們知道它的健康指標數據來源於HealthCountsStream這個數據流:統計時間窗口裏面各桶的值,彙總爲HealthCounts對象輸出。

可以簡要複習下HealthCounts這個類,它記錄着滑動窗口期間的請求數,包括:總數、失敗數、失敗百分比。它會統計如下事件:

  1. HystrixEventType.SUCCESS
  2. HystrixEventType.FAILURE
  3. HystrixEventType.TIMEOUT
  4. HystrixEventType.THREAD_POOL_REJECTED
  5. HystrixEventType.SEMAPHORE_REJECTED

這些事件中除了1其它均爲失敗。另外2-4不就正好對應着文首寫着的觸發fallback的前四種情況嗎?


觸發fallback的情況和熔斷器事件類型的對應關係

下面繪製一張表格表達其對應關係:

失敗情況 原始異常類型 是否觸發fallback 是否納入熔斷器統計 事件類型
short-circuited短路 RuntimeException SHORT_CIRCUITED
threadpool-rejected線程池拒絕 RejectedExecutionException THREAD_POOL_REJECTED
semaphore-rejected信號量拒絕 RuntimeException SEMAPHORE_REJECTED
timed-out超時 TimeoutException TIMEOUT
failed失敗 目標方法拋出的異常類型 FAILURE
HystrixBadRequestException 該異常亦由目標方法拋出

對此表格做如下幾點說明:

  1. 事件類型均爲HystrixEventType類型,本處前綴省略
  2. 這裏指的是原始異常類型,因爲最終經過fallback處理後都會被包爲HystrixRuntimeException
  3. 實際上失敗情況HystrixBadRequestExceptionfailed失敗同屬目標方法拋出的異常,只是前者比較特殊而已~

結合熔斷器統計數據類HealthCounts關心的幾個事件類型來說:除了HystrixBadRequestException異常導致的失敗,其它均會被收集作爲斷路器的指標數據。

說明:short-circuited短路這種case就不用收集啦,因爲都已經短路了,就沒必要再收集了,否則斷路器永遠都自愈不回來就尷尬了


爲何不會觸發熔斷器?

所有的命令執行,最終在executeCommandAndObserve()方法內:

AbstractCommand:

	private Observable<R> executeCommandAndObserve(final AbstractCommand<R> _cmd) {
		...
        return execution.doOnNext(markEmits)
                .doOnCompleted(markOnCompleted)
                .onErrorResumeNext(handleFallback)
                .doOnEach(setRequestContext);
	}

其它部分本文不用關心,僅需關心onErrorResumeNext(handleFallback)這個函數,它的觸發條件是:發射數據時(目標方法執行時)出現異常便會回調此函數,因此需要看看handleFallback的邏輯。

說明:正常執行(成功)時不會回調此函數,而是回調的doOnCompleted(markOnCompleted)哦~


handleFallback

顧名思義,它是用於處理fallback的函數。

Func1<Throwable, Observable<R>> handleFallback = new Func1<Throwable, Observable<R>>() {

	// 當大聲異常時,回調此方法,該異常就是t
    @Override
    public Observable<R> call(Throwable t) {
    	// 若t就是Exception類型,那麼t和e一樣
    	// 若不是Exception類型,比如是Error類型。那就用Exception把它包起來
    	// new Exception("Throwable caught while executing.", t);
        Exception e = getExceptionFromThrowable(t);
        // 把異常寫進結果裏
        executionResult = executionResult.setExecutionException(e);

		// ==============針對不同異常的處理==============
        if (e instanceof RejectedExecutionException) {
            return handleThreadPoolRejectionViaFallback(e);
        } else if (t instanceof HystrixTimeoutException) {
            return handleTimeoutViaFallback();
        } else if (t instanceof HystrixBadRequestException) {
            return handleBadRequestByEmittingError(e);
        } else {
            return handleFailureViaFallback(e);
        }
    }
};

以上源碼,針對不同異常類型的處理方法,除了針對HystrixBadRequestException異常類型沒講述過,其它均在上篇文章有過詳細闡述。下面具體看看handleBadRequestByEmittingError()對該異常的處理。


handleBadRequestByEmittingError()

此方法專門用於處理HystrixBadRequestException異常類型。

AbstractCommand:

    private Observable<R> handleBadRequestByEmittingError(Exception underlying) {
        Exception toEmit = underlying;

        try {
            long executionLatency = System.currentTimeMillis() - executionResult.getStartTimestamp();
            // 請注意:這裏發送的是BAD_REQUEST事件哦~~~~
            eventNotifier.markEvent(HystrixEventType.BAD_REQUEST, commandKey);
            executionResult = executionResult.addEvent((int) executionLatency, HystrixEventType.BAD_REQUEST);
            // 留個鉤子:調用者可以對異常類型進行偷天換日
            Exception decorated = executionHook.onError(this, FailureType.BAD_REQUEST_EXCEPTION, underlying);


			// 如果調用者通過hook處理完後還是HystrixBadRequestException類型,那就直接把數據發射出去
			// 若不是,那就不管,還是發射原來的異常類型
            if (decorated instanceof HystrixBadRequestException) {
                toEmit = decorated;
            } else {
                logger.warn("ExecutionHook.onError returned an exception that was not an instance of HystrixBadRequestException so will be ignored.", decorated);
            }
        } catch (Exception hookEx) {
            logger.warn("Error calling HystrixCommandExecutionHook.onError", hookEx);
        }
        return Observable.error(toEmit);
    }

這就是HystrixBadRequestException特殊對待邏輯,它發出的事件類型是HystrixEventType.BAD_REQUEST,而此事件類型是不會被HealthCounts作爲健康指標所統計的,因此它並不會觸發熔斷器。


使用場景

瞭解了HystrixBadRequestException的這個特性後,使用場景可根據具體業務而定嘍。比如我們最爲常用的場景便是在Feign上自定義一個錯誤解碼器ErrorDecoder,然後針對於錯誤碼是400的響應統一轉換爲HystrixBadRequestException異常拋出,這樣是比較優雅的一種實踐方案。


總結

Hystrix拋出HystrixBadRequestException異常爲何不會觸發熔斷?這個話題就先聊到這了,到此篇爲止講述完了Hystrix執行時所有的異常狀態的處理方式。

小總結一下:Hystrix對異常HystrixBadRequestException的處理髮送的事件類型HystrixEventType.BAD_REQUEST,而該事件類型對負責給熔斷器收集指標數據的HealthCounts是無效的,所以它並不會觸發熔斷器。
分隔線

聲明

原創不易,碼字不易,多謝你的點贊、收藏、關注。把本文分享到你的朋友圈是被允許的,但拒絕抄襲。你也可【左邊掃碼/或加wx:fsx641385712】邀請你加入我的 Java高工、架構師 系列羣大家庭學習和交流。
往期精選

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