在微服務框架中,通過rest api的方式調用其他服務是很正常的事情。在spring生態系統中,一個流行的REST客戶端是Feign,這是因為它的聲名式風格和添加不同配置的DRY方式。
這篇博客中,我會討論關於feign客戶端的重試機制。本能的,我們會這樣實現,在try catch和while循環中編寫api調用語句,並為另一個api調用編寫代碼,直到滿足條件。這也許能符合我們的目的,但是這會使得我們的代碼丑陋且無法實現。
理想情況下,所有東西完美運行,且我們不需要重試任何HTTP請求。因此,在feign中,默認是不啟用重試的。然后,完美是不存在的,對於一個tcp包來說,在網絡中有數百萬種方法會死掉。所以,為了啟用重試,你必須把下面的代碼放在你的客戶端配置中。
@Bean
public Retryer retryer() {
return new Retryer.Default();
}
你可以在default方法中傳一些參數,比如:間隔時間、最大重試次數等,否則它會以1秒間隔重試5次。
這僅僅會讓feign在碰到IO異常的時候重試。這有點道理,對吧? X 應該重試去獲取Y,僅僅當Y不可達的時候。但這並不是經常發生的。有可能,由於Y和Z之間的連接斷了,導致Y返回5XX的錯誤碼,並且你想在這種情況下重試。要使用它,你必須拋出RetryableException。為了實現這樣的目的,我們需要實現ErrorDecoder類。代碼像這樣:
public class MyErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
@Override
public Exception decode(String s, Response response) {
Exception exception = defaultErrorDecoder.decode(s, response);
if(exception instanceof RetryableException){
return exception;
}
if(response.status() == 504){
return new RetryableException("504 error", response.request().httpMethod(), null );
}
return exception;
}
}
為了使上述代碼生效,你必須把下面的配置放到application properties文件中:
feign.client.config.default.error-decoder=com.example.somepackage.MyErrorDecoder
現在,事情已安排妥當,讓我們看看MyErrorDecoder
這個類都干了些什么。它實現了ErrorDecoder
類並且重寫了它的decode方法,這很明顯。在decode方法內部,首先我們檢查了拋出的異常是不是已經是RetryableException
。如果已經是RetryableException
,那么這是feign自己拋出的異常,並且如果我們返回該異常,feign就會自己進行重試。
如果異常不是RetryableException
,第二段代碼會執行。在這段代碼中,我們檢查返回狀態是不是504。如果是,我們手動返回一個RetryableException
。
我們可以在errorDecoder
中干很多事情。想象一個場景,你想在任何5XX的錯誤碼時進行重試,無論這是否是你的實際場景。那么我們應該怎么做?編寫一堆if/else嘛?不,你不需要,你只需要:
if (HttpStatus.valueOf(response.status()).is5xxServerError()) {
return new RetryableException("Server error", response.request().httpMethod(), null);
}
下面,也是自定義重試機制的一個方法。你為啥要這么做?我的場景時,當發生每次重試的時候,我先要打印log。為了定制這個retryer,首先刪除配置中的默認retryer。然后創建一個模塊,像這樣:
@Slf4j
@Component
@NoArgsConstructor
public class CustomRetryer implements Retryer {
private int retryMaxAttempt;
private long retryInterval;
private int attempt = 1;
public CustomRetryer(int retryMaxAttempt, Long retryInterval) {
this.retryMaxAttempt = retryMaxAttempt;
this.retryInterval = retryInterval;
}
@Override
public void continueOrPropagate(RetryableException e) {
log.info("Feign retry attempt {} due to {} ", attempt, e.getMessage());
if(attempt++ == retryMaxAttempt){
throw e;
}
try {
Thread.sleep(retryInterval);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
}
@Override
public Retryer clone() {
return new CustomRetryer(6, 2000L);
}
}
這里我們的CustomRetryer
重寫了continueOrPropagate
和clone
方法,這是feign默認retryer的方法。clone方法中,我們以需要的參數創建了一個CustomRetryer
,這里6是最大重試次數,2000L時每次重試的間隔時間。
在continueOrPropagate
方法中,你可以定制你的重試機制。記住,為了停止重試並且傳播錯誤信息,你必須拋出這個方法收到的retryable異常。否則,它會繼續重試。在這個例子中,我們在嘗試我們設定的最大重試次數之后,拋出這個異常,否則它會在繼續下一次重試之前,等待間隔時間(參數)。
到目前為止,我們看到的是如何創建一個自定義的錯誤解碼器和重傳器,以根據我們的需要擴展feign的可靠性。如果您以這種方式創建錯誤解碼器和重試器,它將為您添加到項目中的任意數量的feign客戶端工作。但是,想象一個場景,對於不同的client,你想要不通的重試機制,或者對嶼其他的的client,不進行重試。你要怎么做?給不通的client,綁定不通的重試器和編碼器是很容易的。像這樣配置就行:
feign.client.config.default.<your_client_name>.error-decoder=com.example.somepackage.MyErrorDecoderfeign.client.config.client1.retryer=com.example.somepackage.CustomRetryer
重試快樂!!
原文地址:https://medium.com/swlh/how-to-customize-feigns-retry-mechanism-b472202be331