Feign如何重試失敗的調用REST Api
- Spring Cloud
一、簡介
通過 REST 端點調用外部服務是一種常見的活動,像 Feign 這樣的庫非常簡單。但是,在此類通話期間,很多事情都可能出錯。其中會出現許多是隨機的或暫時的問題。
在本教程中,我們將學習如何重試失敗的調用並創建更具彈性的 REST 客戶端。
2. Feign 客戶端設置
首先,讓我們創建一個簡單的 Feign 客戶端構建器,稍後我們將通過重試功能對其進行增強。我們將使用OkHttpClient
作為 HTTP 客戶端。此外,我們將使用GsonEncoder
和GsonDecoder
對請求和響應進行編碼和解碼。最後,我們需要指定目標的 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,我們可以讓它使用可用的 bean 自動連接 Feign 客戶端。
3. Feign Retryer
幸運的是,重試能力是在 Feign 中烘焙的,只需要配置它們。我們可以通過向客戶端構建器Retryer
接口的實現來做到這一點。
它最重要的方法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,我們可以使用@Component
NaiveRetryer
,或者在配置類中定義一個 bean,讓 Spring 完成剩下的工作:
@Bean
public Retryer retryer() {
return new NaiveRetryer();
}
3.2.默認實現
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 重試任何調用,我們可以為客戶端構建器Retryer.NEVER_RETRY
它每次都會簡單地傳播異常。
4. 創建可重試異常
在上一節中,我們學會了控制重試調用的頻率。現在讓我們看看如何控制何時重試調用以及何時簡單地拋出異常。
4.1 ErrorDecoder
和RetryableException
當我們收到錯誤的響應時,Feign 將其傳遞給ErrorDecoder
接口的一個實例,該接口決定如何處理它。最重要的是,解碼器可以將異常映射到RetryableException,
的實例,使Retryer
能夠重試調用。 **ErrorDecoder
的默認實現僅在響應包含“Retry-After”標頭時RetryableExeception
**最常見的是,我們可以在 503 Service Unavailable 響應中找到它。
這是很好的默認行為,但有時我們需要更加靈活。例如,我們可能正在與一個外部服務通信,該服務不時隨機響應 500 Internal Server Error,而我們無權修復它。我們能做的就是重試調用,因為我們知道它下次可能會起作用。為此,我們需要編寫一個自定義的ErrorDecoder
實現。
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,
null,
response.request());
}
return exception;
}
}
請注意,在這種情況下,我們創建並返回異常,而不是拋出它。
最後,我們需要在客戶端構建器中插入我們的解碼器:
public static <T> T createClient(Class<T> type, String uri) {
return Feign.builder()
// ...
.errorDecoder(new Custom5xxErrorDecoder())
// ...
}
五、總結
在本文中,我們學習瞭如何控制 Feign 庫的重試邏輯。我們研究了Retryer
界面以及如何使用它來操縱時間和重試嘗試次數。然後我們創建了我們的ErrorDecoder
來控制哪些響應需要重試。