【翻譯】怎么自定義feign的重試機制


在微服務框架中,通過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重寫了continueOrPropagateclone方法,這是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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM