Feign 重试
1. 简介
通过 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. 假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. 默认实现
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. 不重试
如果我们不希望 Feign 重试任何调用,我们可以为客户端构建器提供Retryer.NEVER_RETRY实现。它每次都会简单地传播异常。
4. 创建可重试异常
在上一节中,我们学会了控制重试调用的频率。现在让我们看看如何控制何时重试调用以及何时简单地抛出异常。
4.1. ErrorDecoder和RetryableException
当我们收到错误的响应时,Feign 将其传递给ErrorDecoder接口的一个实例,该接口决定如何处理它。最重要的是,解码器可以将异常映射到 RetryableException 的实例,使Retryer能够重试调用。ErrorDecoder的默认实现仅在响应包含“Retry-After”标头时创建RetryableException实例。**最常见的是,我们可以在 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())
// ...
}