一、简介
通过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. Feign 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.默认实现
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.1ErrorDecoder
和RetryableException
当我们收到错误的响应时,Feign 将其传递给ErrorDecoder
接口的一个实例,该接口决定如何处理它。最重要的是,解码器可以将异常映射到RetryableException,
的实例,使Retryer
能够重试调用。**ErrorDecoder
的默认实现仅在响应包含“Retry-After”标头时RetryableExeception
**最常见的是,我们可以在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()) // ... }
五、总结
在本文中,我们学习了如何控制Feign 库的重试逻辑。我们研究了Retryer
界面以及如何使用它来操纵时间和重试尝试次数。然后我们创建了我们的ErrorDecoder
来控制哪些响应需要重试。
0 评论