HttpClient的兩種重試機制

本文基於 HttpClient 4.5.13

使用 http 請求外部服務時,由於網絡或者服務本身的不穩定性,經常需要重試。重試當然可以通過手擼代碼實現,但更好的方式是通過現有的機制去實現。 HttpClient 中支持兩種重試:

異常重試。
服務不可用重試。
異常重試
HttpClient 執行時會拋出兩種異常:

java.io.IOException
ClientProtocolException

java.io.IOException 被認爲是非致命性且可恢復的,而 ClientProtocolException 被認爲是致命性的,不可恢復。

處理的時候要注意, ClientProtocolException 是 java.io.IOException 的子類。

如果是這樣創建 HttpClient 的

CloseableHttpClient httpClient = HttpClients.custom().build();

異常重試是默認開啓的,具體代碼可以參考 HttpClientBuilder.build() 方法,下面是相關的代碼

// Add request retry executor, if not disabled
if (!automaticRetriesDisabled) {
    HttpRequestRetryHandler retryHandlerCopy = this.retryHandler;
    if (retryHandlerCopy == null) {
        retryHandlerCopy = DefaultHttpRequestRetryHandler.INSTANCE;
    }
    execChain = new RetryExec(execChain, retryHandlerCopy);
}

automaticRetriesDisabled 是一個 boolean 類型的變量,默認爲 false ,所以條件默認是成立的,如果沒有設置 HttpRequestRetryHandler 就會用一個默認的。

DefaultHttpRequestRetryHandler 主要有三個成員變量

retryCount
requestSentRetryEnabled
nonRetriableClasses

默認的實例變量設置如下

retryCount=3 ,最多重試3次。
requestSentRetryEnabled=false ,發送成功的就不會重試了
nonRetriableClasses 包含了以下四種:InterruptedIOException UnknownHostException ConnectException SSLException
重試的執行邏輯在 org.apache.http.impl.execchain.RetryExec ,有興趣的可以去看下。

默認的是否重試邏輯如下

@Override
public boolean retryRequest(final IOException exception, final int executionCount, final HttpContext context) {
    Args.notNull(exception, "Exception parameter");
    Args.notNull(context, "HTTP context");
    if (executionCount > this.retryCount) {
        // Do not retry if over max retry count
        // 超過重試次數不重試
        return false;
    }
    // 如果是忽略的異常不重試
    if (this.nonRetriableClasses.contains(exception.getClass())) {
        return false;
    }
    for (final Class<? extends IOException> rejectException : this.nonRetriableClasses) {
        if (rejectException.isInstance(exception)) {
            return false;
        }
    }
    final HttpClientContext clientContext = HttpClientContext.adapt(context);
    final HttpRequest request = clientContext.getRequest();

    if(requestIsAborted(request)){
        return false;
    }
    // 冪等方法可以重試
    if (handleAsIdempotent(request)) {
        // Retry if the request is considered idempotent
        return true;
    }
    // 如果請求沒有發送或者發送了也重試
    if (!clientContext.isRequestSent() || this.requestSentRetryEnabled) {
        // Retry if the request has not been sent fully or
        // if it's OK to retry methods that have been sent
        return true;
    }
    // otherwise do not retry
    return false;
}

這裏要注意下冪等方法, post 和 put 都不是,所以那裏的判斷不會成立,但是如果 requestSentRetryEnabled 設置爲 true ,還是會重發的,那就需要保證被調用的接口再處理 post 和 put 的請求時是冪等的。

有些人可能會遇到問題,比如報了 SocketTimeoutException 的異常,但是沒有重試,這是因爲 SocketTimeoutException 是 InterruptedIOException 的子類,默認會被忽略。如果需要重試,可以自定義一個 HttpRequestRetryHandler ,然後再設置就可以了。

HttpClients.custom().setRetryHandler(httpRequestRetryHandler).build();
實際上使用的時候繼承 DefaultHttpRequestRetryHandler ,然後擴展一些自己的實現就很方便。

如果想禁用調重試也很簡單

HttpClients.custom().disableAutomaticRetries().build();

服務不可用重試
有的時候,請求成功了,但是 http 狀態碼可能不是 2xx,這種情況也需要重試。 HttpClient中提供了在服務不可用時進行重試的機制。

重試執行的邏輯在 org.apache.http.impl.execchain.ServiceUnavailableRetryExec ,有興趣可以看下。

HttpClient 中提供了默認的策略,但是沒有默認開啓,需要自己設置

DefaultServiceUnavailableRetryStrategy serviceUnavailableRetryStrategy = new DefaultServiceUnavailableRetryStrategy();
httpClient = HttpClients.custom().setServiceUnavailableRetryStrategy(serviceUnavailableRetryStrategy).build();

重試的邏輯

@Override
public boolean retryRequest(final HttpResponse response, final int executionCount, final HttpContext context) {
    return executionCount <= maxRetries &&
        response.getStatusLine().getStatusCode() == HttpStatus.SC_SERVICE_UNAVAILABLE;
}

當沒有超過重試次數,且返回碼爲503的時候進行重試,當然也可以自定義 ServiceUnavailableRetryStrategy 來實現自己的需求。

另外還支持設置重試請求的間隔時間。

來源:https://www.tuicool.com/articles/R73AzmF

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