重试Feign调用

2023/05/11

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. Feign重试

幸运的是,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())
        // ...
}

5. 总结

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

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章