In the microservice framework, it is normal to call other services through rest api. In the spring ecosystem, a popular REST client is Feign, because of its reputation style and DRY method of adding different configurations.

In this blog, I will discuss the retry mechanism of the feign client. Instinctively, we will do this by writing api call statements in try catch and while loops, and write code for another api call until the conditions are met. This may serve our purpose, but it will make our code ugly and impossible to achieve.

Ideally, everything works perfectly, and we don't need to retry any HTTP requests. Therefore, in feign, retry is not enabled by default. Then, perfection does not exist. For a tcp packet, there are millions of ways to die in the network. So, in order to enable retry, you must put the following code in your client configuration.

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

You can pass some parameters in the default method, such as: interval time, maximum number of retries, etc., otherwise it will retry 5 times at an interval of 1 second.

This will only make Feign retry when it encounters an IO exception. This makes sense, right? X should retry to get Y, only when Y is unreachable. But this does not happen often. It is possible that the connection between Y and Z is broken, causing Y to return an error code of 5XX, and you want to try again in this case. To use it, you must throw a RetryableException. In order to achieve this purpose, we need to implement the ErrorDecoder class. The code looks like this:

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;
    }
}

In order to make the above code effective, you must put the following configuration in the application properties file:

feign.client.config.default.error-decoder=com.example.somepackage.MyErrorDecoder

Now that things have been arranged, let us see what the MyErrorDecoder class does. It implements the ErrorDecoder class and rewrites its decode method, which is obvious. Inside the decode method, first we check whether the thrown exception is already RetryableException . If it is already RetryableException , then this is an exception thrown by feign itself, and if we return the exception, feign will try again by itself.

If the exception is not RetryableException , the second code will be executed. In this code, we check whether the return status is 504. If it is, we manually return a RetryableException .

We can do many things errorDecoder Imagine a scenario where you want to retry any 5XX error codes, regardless of whether this is your actual scenario or not. So what should we do? Write a bunch of if/else? No, you don’t need it, you just need:

if (HttpStatus.valueOf(response.status()).is5xxServerError()) {
    return new RetryableException("Server error", response.request().httpMethod(), null);
}

The following is also a way to customize the retry mechanism. Why are you doing this? In my scenario, when each retry occurs, I first need to print the log. In order to customize this retryer, first delete the default retryer in the configuration. Then create a module like this:

@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);
    }
}

Here our CustomRetryer rewrites the continueOrPropagate and clone methods, which is the default retryer method of feign. In the clone method, we created a CustomRetryer with the required parameters, where 6 is the maximum number of retries, and the interval between each retry at 2000L.

In the continueOrPropagate method, you can customize your retry mechanism. Remember, in order to stop retrying and propagate error messages, you must throw the retryable exception received by this method. Otherwise, it will continue to try again. In this example, we throw this exception after trying the maximum number of retries we set, otherwise it will wait for the interval (parameter) before continuing to the next retry.

So far, what we have seen is how to create a custom error decoder and retransmitter to extend the reliability of feign according to our needs. If you create error decoders and retryers in this way, it will work for any number of feign clients you add to your project. However, imagine a scenario where you want an unreasonable retry mechanism for different clients, or do not retry for other clients. What are you going to do? It is very easy to bind unreasonable retryers and encoders to unreasonable clients. Just configure it like this:

feign.client.config.default.<your_client_name>.error-decoder=com.example.somepackage.MyErrorDecoderfeign.client.config.client1.retryer=com.example.somepackage.CustomRetryer

Happy retrying! !

Original address: https://medium.com/swlh/how-to-customize-feigns-retry-mechanism-b472202be331


程序员伍六七
201 声望597 粉丝