概述
通过REST端点调用外部服务是常见的操作,Feign库使得这一过程变得简单易行。然而,在这些调用过程中可能会遇到各种问题,许多问题是随机或暂时的。本教程将介绍如何处理失败的调用,创建更健壮的REST客户端。
1. 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,可以让它自动将Feign客户端与可用的bean绑定。
2. Feign的重试器
幸运的是,Feign内置了重试能力,只需要正确配置即可。我们可以通过向客户端构建器提供Retryer接口的实现来做到这一点。
其最重要的方法continueOrPropagate
接受一个RetryableException
参数并返回无值。在执行时,它要么抛出异常,要么成功退出(通常是在休眠后)。如果它不抛出异常,Feign将继续尝试调用。如果抛出异常,它将被传播,并最终以错误结束调用。
2.1. 简单实现
让我们编写一个非常简单的重试器实现,每次失败后都会等待一秒钟再尝试:
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,可以将NaiveRetryer
注解为@Component
注解,或者在配置类中定义一个bean,让Spring完成其余工作:
@Bean
public Retryer retryer() {
return new NaiveRetryer();
}
2.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))
// ...
}
2.3. 不进行重试
如果我们不想让Feign对任何调用进行重试,可以在客户端构建器中提供Retryer.NEVER_RETRY
实现。它每次都会直接传播异常。
3. 创建可重试的异常
在上一节中,我们学习了如何控制重试频率。现在来看看如何决定何时重试调用,何时抛出异常。
3.1. ErrorDecoder
和RetryableException
当收到错误响应时,Feign会将其传递给一个ErrorDecoder
接口的实例,由该实例决定如何处理。最重要的是,解码器可以将异常映射为RetryableException
实例,从而让Retryer
重试调用。默认的ErrorDecoder
实现只有在响应包含"Retry-After"头时才会创建RetryableExeception
实例。通常,我们会在503服务不可用的响应中找到它。
这是合理的默认行为,但有时我们需要更具灵活性。例如,我们可能与一个外部服务通信,它偶尔会随机返回500内部服务器错误,而我们无法修复。在这种情况下,我们可以重试调用,因为我们知道下次可能会正常工作。为了实现这一点,我们需要编写自定义的ErrorDecoder
实现。
3.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())
// ...
}
4. 总结
在这篇文章中,我们学习了如何控制Feign库的重试逻辑。我们探讨了Retryer
接口及其如何用于操纵重试的时间和次数。然后我们创建了一个自定义的ErrorDecoder
来控制哪些响应值得重试。
如往常一样,所有代码示例均可在GitHub上找到。