知識庫 / REST RSS 訂閱

重試Feign調用

REST
HongKong
6
03:39 AM · Dec 06 ,2025

1. 引言

通過 REST 端點調用外部服務是常見的活動,而像 Feign 這樣的庫極大地簡化了這一過程。然而,在這些調用過程中可能會出現各種問題。許多問題是隨機或暫時的。

在本教程中,我們將學習如何重試失敗的調用並創建更健壯的 REST 客户端。

2. Feign 客户端設置

首先,讓我們創建一個簡單的 Feign 客户端構建器,稍後我們會通過添加重試功能來增強它。 我們將使用 OkHttpClient 作為 HTTP 客户端。 此外,我們還將使用 GsonEncoderGsonDecoder 用於編碼和解碼請求和響應。 最後,我們需要指定目標 URI 和響應類型:

public class ResilientFeignClientBuilder {
    public static <T> T createClient(Class<T> type, String uri) {
        return Feign.builder()
          .client(new OkHttpClient())
          .encoder(new GsonEncoder())
          .decoder(new GsonDecoder())
          .target(type, uri);
    }
}

當然,以下是翻譯後的內容:

或者,如果使用Spring,我們可以讓它自動注入Feign客户端,並使用可用的Bean。

3. Feign 重試器

幸運的是,Feign 已經內置了重試功能,只需要進行配置。 我們可以通過向客户端構建器提供 重試器接口的實現來完成此操作。

它的最重要方法,continueOrPropagate接受 RetryableException作為參數,並返回空。 在執行過程中,它要麼拋出異常,要麼成功退出(通常是在睡眠後)。 如果它沒有拋出異常,Feign 將繼續重試調用。 如果異常被拋出,它將被傳播,並有效地結束調用,伴隨錯誤。

3.1. 樸素實現

以下是一個非常簡單的 Retryer 實現,它會始終在等待一秒後重試調用:

public class NaiveRetryer implements feign.Retryer {
    @Override
    public void continueOrPropagate(RetryableException e) {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
            throw e;
        }
    }
}

由於 Retryer 實現的 Cloneable 接口,因此我們也需要覆蓋 clone 方法。

@Override
public Retryer clone() {
    return new NaiveRetryer();
}

最後,我們需要將我們的實現添加到客户端構建器中:

public static <T> T createClient(Class<T> type, String uri) {
    return Feign.builder()
      // ...
      .retryer(new NaiveRetryer())    
      // ...
}

當然,以下是翻譯後的內容:

或者,如果使用 Spring,我們可以使用 NaiveRetryer 註解並添加 @Component 註解,或者在配置類中定義一個 Bean,讓 Spring 完成剩餘的工作:

@Bean
public Retryer retryer() {
    return new NaiveRetryer();
}

3.2. 默認實現

Feign 提供了一個合理的 Retryer 接口默認實現。它只會重試指定次數,從一個時間間隔開始,並隨着每次重試增加到提供的最大值。以下是設置:起始間隔為 100 毫秒,最大間隔為 3 秒,最大嘗試次數為 5 次:

public static <T> T createClient(Class<T> type, String uri) {
    return Feign.builder()
// ...
      .retryer(new Retryer.Default(100L, TimeUnit.SECONDS.toMillis(3L), 5))    
// ...
}

3.3. 不進行重試

如果不想讓 Feign 永遠重試任何調用,我們可以將 <em >Retryer.NEVER_RETRY</em> 實現提供給客户端構建器。它會每次都簡單地傳播異常。

4. 創建可重試異常

在上一節中,我們學習瞭如何控制重試的頻率。現在,讓我們看看如何控制何時想要重試調用,以及何時想要簡單地拋出異常。

4.1. <em >ErrorDecoder</em ><em >RetryableException</em >>

當我們收到錯誤響應時,Feign會將它傳遞給<em >ErrorDecoder</em >>接口的一個實例,該實例決定如何處理它。 最重要的是,解碼器可以將異常映射到<em >RetryableException</em >>實例,從而使<em >Retryer</em >>能夠重試調用。 <strong >ErrorDecoder>的默認實現僅在響應包含“Retry-After”標頭時才創建RetryableExeception>`實例。 這種行為通常在503 Service Unavailable響應中發現。

那是一個不錯的默認行為,但有時我們需要更靈活。 例如,我們可能正在與一個外部服務進行通信,該服務偶爾會隨機響應500 Internal Server Error,而且我們無法修復它。 我們所能做的就是重試調用,因為我們知道下次它可能會正常工作。 要實現這一點,我們需要編寫一個自定義<em >ErrorDecoder</em >>實現。

4.2. 創建自定義錯誤解碼器

需要我們在自定義解碼器中實現的方法只有一種:decode。它接受兩個參數,一個 String 方法鍵,和一個 Response 對象。如果返回值為 RetryableException 或其他依賴於實現的異常,它將返回一個異常。

我們的 decode 方法將簡單地檢查響應的狀態碼是否大於或等於 500。如果是,它將創建一個 RetryableException。否則,它將返回使用 FeignException 類中的 errorStatus 工廠函數創建的基本 FeignException

public class Custom5xxErrorDecoder implements ErrorDecoder {
    @Override
    public Exception decode(String methodKey, Response response) {
        FeignException exception = feign.FeignException.errorStatus(methodKey, response);
        int status = response.status();
        if (status >= 500) {
            return new RetryableException(
              response.status(),
              exception.getMessage(),
              response.request().httpMethod(),
              exception,
              50L, // The retry interval
              response.request());
        }
        return exception;
    }
}

請注意,在這種情況中,我們創建並返回異常,而不是拋出它。

最後,我們需要將我們的解碼器插入到客户端構建器中:

public static <T> T createClient(Class<T> type, String uri) {
    return Feign.builder()
      // ...
      .errorDecoder(new Custom5xxErrorDecoder())
      // ...
}

5. 總結

本文介紹瞭如何控制 Feign 庫中的重試邏輯。我們深入研究了 <em >Retryer</em> 接口及其在控制重試時間和重試次數方面的應用。此外,我們創建了 <em >ErrorDecoder</em> 以控制哪些響應應觸發重試。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.