【OpenFeign 】OpenFeign 下的重试器的执行过程分析

news/2024/10/2 10:29:17

1  前言

上节我们看了下 OpenFeign 里的重试,在从源码的角度看它的执行原理的时候,又意外的遇到了一个【OpenFeign 】OpenFeign 下未开启重试,服务却被调用了两次 的问题的分析,那本节我们就来看看重试器的一个入场以及执行的过程。

2  源码分析

首先我们要知道在默认的情况下,OpenFeign 也是有重试器的,只不过是 Retryer 接口中的  NEVER_RETRY:

// Retryer 接口
Retryer NEVER_RETRY = new Retryer() {@Overridepublic void continueOrPropagate(RetryableException e) {throw e;}@Overridepublic Retryer clone() {return this;}
};

并且在 FeignClientsConfiguration 配置中:

@Configuration(proxyBeanMethods = false)
public class FeignClientsConfiguration {// ...// 当没有重试器的情况下,配置默认的 Retryer.NEVER_RETRY;
    @Bean@ConditionalOnMissingBeanpublic Retryer feignRetryer() {return Retryer.NEVER_RETRY;}// ...
}

我们在调试的时候,也可以看到:

那么接下来我们要分析重试器的执行原理的话,还是要从两个角度看起,一个是创建过程或者叫入场时机,因为我们的 Feign 会被 FeignClientFactoryBean 来负责创建,创建的时候会设置重试器 二就是执行过程。

2.1  重试器的入场

我们还是从 FeignClientFactoryBean 创建 Feign 代理的入口开始看起:

// FeignClientFactoryBean 
@Override
public Object getObject() {// 调用内部的 getTarget 方法return getTarget();
}
<T> T getTarget() {// 获取 FeignContext 这个是 feign 的上下文对象FeignContext context = beanFactory != null? beanFactory.getBean(FeignContext.class): applicationContext.getBean(FeignContext.class);// 构建 feign 的 builder 建造器Feign.Builder builder = feign(context);// 处理一些基础属性// 如果没有 url 就创建负载均衡型的客户端来请求  一般我们都是用这个if (!StringUtils.hasText(url)) {if (LOG.isInfoEnabled()) {LOG.info("For '" + name+ "' URL not provided. Will try picking an instance via load-balancing.");}if (!name.startsWith("http")) {url = "http://" + name;}else {url = name;}url += cleanPath();return (T) loadBalance(builder, context,new HardCodedTarget<>(type, name, url));}if (StringUtils.hasText(url) && !url.startsWith("http")) {url = "http://" + url;}String url = this.url + cleanPath();// 如果你全局上下文执行了用什么型号的客户端去请求 就采用你指定的Client client = getOptional(context, Client.class);if (client != null) {if (client instanceof LoadBalancerFeignClient) {// not load balancing because we have a url,// but ribbon is on the classpath, so unwrapclient = ((LoadBalancerFeignClient) client).getDelegate();}if (client instanceof FeignBlockingLoadBalancerClient) {// not load balancing because we have a url,// but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((FeignBlockingLoadBalancerClient) client).getDelegate();}if (client instanceof RetryableFeignBlockingLoadBalancerClient) {// not load balancing because we have a url,// but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();}builder.client(client);}// 创建代理对象Targeter targeter = get(context, Targeter.class);return (T) targeter.target(this, builder, context,new HardCodedTarget<>(type, name, url));
}

我们的 Feign 代理对象的创建跟 Feign.Builder 贴合很近,所以 Feign.Builder 设置的是什么重试器,我们最后的 Feign 的执行也是用的什么重试器。为什么这么说呢?我这里就不一点点带大家看了,我简单画个主流向图:

可以看到当我们的 Feign.Builder 创建并初始化重试器后,是一直向后传递的,最后交给方法的处理器 SynchronousMethodHandler。

所以我们这里看下 Feign.Builder 的重试器的设置:

// FeignClientFactoryBean 
protected Feign.Builder feign(FeignContext context) {// 日志相关FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);Logger logger = loggerFactory.create(type);// @formatter:offFeign.Builder builder = get(context, Feign.Builder.class)// required values
            .logger(logger).encoder(get(context, Encoder.class)).decoder(get(context, Decoder.class)).contract(get(context, Contract.class));// @formatter:on// 填充
    configureFeign(context, builder);// 自定义行为
    applyBuildCustomizers(context, builder);return builder;
}
// configureFeign
// 这个配置 feign 有一个共同点就是不管是 if else 都会走向 configureUsingConfiguration
protected void configureFeign(FeignContext context, Feign.Builder builder) {FeignClientProperties properties = beanFactory != null? beanFactory.getBean(FeignClientProperties.class): applicationContext.getBean(FeignClientProperties.class);FeignClientConfigurer feignClientConfigurer = getOptional(context,FeignClientConfigurer.class);setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());if (properties != null && inheritParentContext) {if (properties.isDefaultToProperties()) {configureUsingConfiguration(context, builder);configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()),builder);configureUsingProperties(properties.getConfig().get(contextId), builder);}else {configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()),builder);configureUsingProperties(properties.getConfig().get(contextId), builder);configureUsingConfiguration(context, builder);}}else {configureUsingConfiguration(context, builder);}
}
// configureUsingConfiguration
protected void configureUsingConfiguration(FeignContext context,Feign.Builder builder) {Logger.Level level = getInheritedAwareOptional(context, Logger.Level.class);if (level != null) {builder.logLevel(level);}// 获取重试器并放进 BuilderRetryer retryer = getInheritedAwareOptional(context, Retryer.class);if (retryer != null) {builder.retryer(retryer);}// ...
}

好啦,到这里重试器的入场就看到这里了。

2.2  重试器的执行

按照上边的流图,最后 Feign 执行的时候,会进入到 ReflectiveFeign 的 invoke:

// FeignInvocationHandler 
static class FeignInvocationHandler implements InvocationHandler {private final Target target;private final Map<Method, MethodHandler> dispatch;FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {this.target = checkNotNull(target, "target");this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if ("equals".equals(method.getName())) {try {Object otherHandler =args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;return equals(otherHandler);} catch (IllegalArgumentException e) {return false;}} else if ("hashCode".equals(method.getName())) {return hashCode();} else if ("toString".equals(method.getName())) {return toString();}// 接着走向 SynchronousMethodHandler 的 invokereturn dispatch.get(method).invoke(args);}...
}

那我们继续看 SynchronousMethodHandler 的 invoke:

// SynchronousMethodHandler 
@Override
public Object invoke(Object[] argv) throws Throwable {RequestTemplate template = buildTemplateFromArgs.create(argv);Options options = findOptions(argv);// 每次请求都会把重试器复制一份Retryer retryer = this.retryer.clone();// 一直循环 // 两种方式退出// 1、return 正常请求 2、throw 抛出异常while (true) {try {// 执行请求return executeAndDecode(template, options);} catch (RetryableException e) {// 捕获重试异常try {// 执行重试器的 continueOrPropagate 方法
        retryer.continueOrPropagate 方法(e);} catch (RetryableException th) {Throwable cause = th.getCause();if (propagationPolicy == UNWRAP && cause != null) {throw cause;} else {throw th;}}if (logLevel != Logger.Level.NONE) {logger.logRetry(metadata.configKey(), logLevel);}continue;}}
}

可以看到当请求发生异常时,会调用重试器的 continueOrPropagate 方法,那么对于默认不开启重试的 NEVER_RETRY ,就是直接抛出异常,结束调用。

Retryer NEVER_RETRY = new Retryer() {@Overridepublic void continueOrPropagate(RetryableException e) {throw e;}@Overridepublic Retryer clone() {return this;}
};

对于 Retryer 内部的还有一个提供的 Default 的重试器,就是根据最大重试次数以及重试间隔时间控制睡眠时间然后继续进入 while 循环,继而继续请求,当达到最大重试次数时,还是异常的话,抛出异常,执行结束。

class Default implements Retryer {private final int maxAttempts;private final long period;private final long maxPeriod;int attempt;long sleptForMillis;public Default() {this(100, SECONDS.toMillis(1), 5);}public Default(long period, long maxPeriod, int maxAttempts) {this.period = period;this.maxPeriod = maxPeriod;this.maxAttempts = maxAttempts;this.attempt = 1;}// visible for testing;protected long currentTimeMillis() {return System.currentTimeMillis();}public void continueOrPropagate(RetryableException e) {if (attempt++ >= maxAttempts) {throw e;}long interval;if (e.retryAfter() != null) {interval = e.retryAfter().getTime() - currentTimeMillis();if (interval > maxPeriod) {interval = maxPeriod;}if (interval < 0) {return;}} else {interval = nextMaxInterval();}try {Thread.sleep(interval);} catch (InterruptedException ignored) {Thread.currentThread().interrupt();throw e;}sleptForMillis += interval;}

好啦,那么重试器的执行也看到这里了。

3  小结

本节我们主要看了下,FeignClient 的重试器的入场以及执行流程,有理解不对的地方,还请指正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/58583.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

WiFi基础(三):802.11ac/ax/be 与 WiFi4、WiFi5、WiFi6、WiFi7

前面我们介绍了 802.11 b/g/n 的一些核心技术和基础概念,本章将介绍目前比较新的 WiFi5 和 WiFi6,以及在今年会发布的 WiFi7。liwen01 2024.09.08 前言 经过二十多年的发展,WiFi 在硬件能力、软件和算法、频谱资源、市场需求、电源与能效方面都有了很大的提升。所以我们能看…

一个类才几百行/搞定各种自定义委托/涵盖各种场景需求/所有委托一网打尽/用法极其简单

一、应用场景某个字段需要提供下拉框进行选择,下拉框可选是否允许编辑。 某个字段需要提供密码框进行输入,密文显示字段值。 某个字段需要提供日期框下拉选择日期时间。 某个字段需要提供微调框设定值。 某个字段需要提供进度条显示字段值。 某个字段列需要禁用。 各种委托控…

军工厂电气工程师到知名互联网公司程序员,我吃饭的家伙有哪些

大家好,我是欧阳。今年刚好是欧阳三十而立之年,虽然没有立起来。这篇文章来聊聊我从一名军工厂电气工程师到某知名互联网公司程序员,这期间我吃饭的家伙都有哪些。 军工厂期间 欧阳大学读的是“电气工程及其自动化专业”,毕业后进入了老家的一个军工厂,成为了一名电气工程…

用 SQL 写的俄罗斯方块游戏「GitHub 热点速览」

在开始介绍上周热门开源项目之前,要插播一条开源新闻:Nginx 已正式迁移至 GitHub。 近日,Nginx 官方宣布将 Nginx 开源项目,从 Mercurial 迁移至 GitHub 代码托管平台,并开始接受 PR 形式的贡献、Issues 问题反馈和功能请求等,GitHub 上的 Nginx 项目终于“活”了!GitHu…

喜欢干净简洁音乐播放器的朋友看过来

大家好,我是晓凡。 不少程序员小伙伴都喜欢边听音乐边敲代码,尤其在一个嘈杂的环境中,一个好的想法、好的思路可能就因为一瞬间的干扰就没了。 这时,如果耳机一戴上,听着音乐能更好的集中注意力;遇到bug也能临危不乱,想出更好的解决办法; 网易云音乐,算是一个相对简洁…

Cisco Catalyst 9000 交换产品系列 IOS XE 17.15.1 发布下载,新增功能概览

Cisco Catalyst 9000 交换产品系列 IOS XE 17.15.1 发布下载,新增功能概览Cisco Catalyst 9000 Series Switches, IOS XE Release 17.15.1 ED 思科 Catalyst 9000 交换产品系列 IOS XE 系统软件 请访问原文链接:https://sysin.org/blog/cisco-catalyst-9000/,查看最新版。原…

在vue3中手写按需加载图片

在我们的网页中.假如使用了大量的图片,每个图片都是需要去访问加载的 这就影响了我们的访问速度,手写一个按需加载组件,就可以解决这个问题 让图片处于页面视图的时候再加载,减轻网页访问负担利用vue3官网给出的钩子 我们常用的就是onMountent 如官网所示为了及时监测,这里使用…

.NET 9 中 LINQ 新增的功能

LINQ介绍 语言集成查询 (LINQ) 是一系列直接将查询功能集成到 C# 语言的技术统称。数据查询历来都表示为简单的字符串,没有编译时类型检查或 IntelliSense 支持。此外,需要针对每种类型的数据源了解不同的查询语言:SQL 数据库、XML 文档、各种 Web 服务等。借助 LINQ,查询成…