Retrying Feign Calls – 重试佯装呼叫

最后修改: 2022年 2月 1日

1. Introduction

1.绪论

Calling external services through the REST endpoint is a common activity that was made very straightforward by libraries like Feign. However, a lot of things can go wrong during such calls. Many of these problems are random or temporary.

通过REST端点调用外部服务是一种常见的活动,像Feign这样的库使之变得非常简单。然而,在这种调用过程中,很多事情都可能出错。其中许多问题是随机的或暂时的。

In this tutorial, we’ll learn how to retry failed calls and make more resilient REST clients.

在本教程中,我们将学习如何重试失败的调用并制作更有弹性的REST客户端。

2. Feign Client Setup

2.伪装客户设置

First, let’s create a simple Feign client builder that we’ll later enhance with retrying features. We’ll use OkHttpClient as the HTTP client. Also, we’ll use GsonEncoder and GsonDecoder for encoding and decoding the requests and the responses. Finally, we’ll need to specify the target’s URI and response type:

首先,让我们创建一个简单的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);
    }
}

Alternatively, if we use Spring, we could let it auto-wire Feign client with available beans.

另外,如果我们使用Spring,我们可以让它自动将Feign客户端与可用的Bean连接起来。

3. Feign Retryer

3.伪装成Retryer

Fortunately, retrying abilities are baked in Feign, and they just need to be configured. We can do that by providing an implementation of the Retryer interface to the client builder.

幸运的是,重试的能力已经在 Feign 中得到了体现,它们只需要被配置。我们可以通过为客户端构建器提供一个Retryer接口的实现来实现这一目标。

Its most important method, continueOrPropagate, accepts RetryableException as an argument and returns nothing. Upon execution, it either throws an exception or exits successfully (usually after sleeping). If it doesn’t throw an exception, Feign will continue to retry the call. If the exception is thrown, it’ll be propagated and will effectively finish the call with an error.

它最重要的方法,continueOrPropagate,接受RetryableException作为一个参数,并且不返回任何东西。在执行时,它要么抛出一个异常,要么成功退出(通常是在睡眠后)。如果它没有抛出异常,Feign将继续重试调用。如果抛出了异常,它将被传播,并将有效地以错误结束调用。

3.1. Naive Implementation

3.1.天真的实施

Let’s write a very simple implementation of Retryer that will always retry calls after waiting one second:

让我们写一个非常简单的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;
        }
    }
}

Because Retryer implements the Cloneable interface, we also needed to override the clone method.

因为Retryer实现了Cloneable接口,我们还需要覆盖clone方法。

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

Finally, we need to add our implementation to client builder:

最后,我们需要将我们的实现添加到客户端构建器中。

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

Alternatively, if we are using Spring, we could annotate NaiveRetryer with @Component annotation or define a bean in the configuration class and let Spring do the rest of the work:

另外,如果我们使用Spring,我们可以用@Component注解来注解NaiveRetryer,或者在配置类中定义一个bean,让Spring完成其余工作。

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

3.2. Default Implementation

3.2.默认执行

Feign provides a sensible default implementation of the Retryer interface. It’ll retry only a given number of times, will start with some time interval, and then increase it with each retry up to provided maximum. Let’s define it with starting interval of 100 milliseconds, the maximum interval of 3 seconds, and the maximum number of attempts of 5:

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. No Retrying

3.3.不重试

If we don’t want Feign to ever retry any calls, we can provide Retryer.NEVER_RETRY implementation to the client builder. It’ll simply propagate the exception every time.

如果我们不希望Feign重试任何调用,我们可以向客户端构建器提供Retryer.NEVER_RETRY实现。它将简单地传播每次的异常。

4. Creating Retryable Exceptions

4.创建可重试的例外情况

In the previous section, we learned to control how often we retry calls. Now let’s see how to control when we want to retry the call and when we want to simply throw the exception.

在上一节中,我们学习了如何控制重试调用的频率。现在让我们看看如何控制什么时候要重试调用,什么时候要简单地抛出异常。

4.1. ErrorDecoder and RetryableException

4.1.ErrorDecoderRetryableException

When we receive an erroneous response, Feign passes it to an instance of the ErrorDecoder interface that decides what to do with it. Most importantly, the decoder can map an exception to an instance of RetryableException, enabling Retryer to retry the call. The default implementation of ErrorDecoder only creates a RetryableExeception instance when the response contains the “Retry-After” header. Most commonly, we can find it in 503 Service Unavailable responses.

当我们收到一个错误的响应时,Feign将其传递给ErrorDecoder接口的一个实例,该实例决定如何处理它。最重要的是,解码器可以将异常映射到RetryableException的实例,使Retryer可以重试调用。ErrorDecoder的默认实现仅在响应包含 “Retry-After “头时创建RetryableExeception实例。最常见的是,我们可以在503 Service Unavailable响应中找到它。

That’s good default behavior, but sometimes we need to be more flexible. For example, we could be communicating with an external service that, from time to time, randomly responds with 500 Internal Server Error, and we have no power to fix it. What we can do is to retry the call because we know that it’ll probably work next time. To achieve that, we’ll need to write a custom ErrorDecoder implementation.

这是很好的默认行为,但有时我们需要更加灵活。例如,我们可能正在与一个外部服务进行通信,该服务不时地随机响应500内部服务器错误,而我们没有能力去解决它。我们能做的是重试调用,因为我们知道,下次可能会成功。为了达到这个目的,我们需要写一个自定义的ErrorDecoder实现。

4.2. Creating Custom Error Decoder

4.2.创建自定义错误解码器

There is only one method that we need to implement in our custom decoder: decode. It accepts two arguments, a String method key, and a Response object. It returns an exception, should it be an instance of RetryableException or some other exception that depends on the implementation.

在我们的自定义解码器中,只有一个方法是我们需要实现的。decode。它接受两个参数,一个是String方法键,另一个是Response对象。它返回一个异常,应该是RetryableException的实例或其他一些取决于实现的异常。

Our decode method will simply check if the response’s status code is higher or equal to 500. If that’s the case, it’ll create RetryableException. If not, it will return basic FeignException created with the errorStatus factory function from the FeignException class:

我们的decode方法将简单地检查响应的状态代码是否高于或等于500。如果是这样的话,它将创建RetryableException。如果不是,它将返回用errorStatus工厂函数从FeignException类创建的基本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;
    }
}

Mind that in this case, we create and return the exception, not throw it.

请注意,在这种情况下,我们创建并返回异常,而不是抛出它。

Finally, we need to plug our decoder in the client builder:

最后,我们需要将我们的解码器插入客户端构建器中。

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

5. Summary

5.摘要

In this article, we learned how to control the retry logic of the Feign library. We looked into the Retryer interface and how it can be used to manipulate the time and number of retry attempts. Then we created our ErrorDecoder to control which responses warrant retry.

在这篇文章中,我们学习了如何控制Feign库的重试逻辑。我们研究了Retryer接口以及如何使用它来操纵重试的时间和次数。然后我们创建了ErrorDecoder来控制哪些响应需要重试。

As always, all code examples can be found over on GitHub.

一如既往,所有的代码实例都可以在GitHub上找到