SpringCloud升级之路2020.0.x版-30. FeignClient 实现重试
需要重试的场景
微服务系统中,会遇到在线发布,一般的发布更新策略是:启动一个新的,启动成功之后,关闭一个旧的,直到所有的旧的都被关闭。Spring Boot 具有优雅关闭的功能,可以保证请求处理完再关闭,同时会拒绝新的请求。对于这些拒绝的请求,为了保证用户体验不受影响,是需要重试的。
云上部署的微服务,对于同一个服务,同一个请求,很可能不会所有实例都同时异常,例如:
- Kubernetes 集群部署的实例,可能同一个虚拟机 Node 在闲时部署了多个不同微服务实例,当压力变大时,就需要迁移和扩容。这时候由于不同的微服务压力不同,当时处于哪一个 Node 也说不定,有的可能处于压力大的,有的可能处于压力小的。对于同一个微服务,可能并不会所有实例位于的 Node 压力都大。
- 云上部署一般会跨可用区部署,如果有一个可用区异常,另一个可用区还可以继续提供服务。
- 某个业务触发了 Bug,导致实例一直在 GC,但是这种请求一般很不常见,不会发到所有实例上。
这时候,就需要我们对请求进行无感知的重试。
重试需要考虑的问题
- 重试需要重试与之前不同的实例,甚至是不处于同一个虚拟机 Node 的实例,这个主要通过 LoadBalancer 实现,可以参考之前的 LoadBalancer 部分。后面的文章,我们还会改进 LoadBalancer
- 重试需要考虑到底什么请求能重试,以及什么异常能重试:
- 假设我们有查询接口,和没有做幂等性的扣款接口,那么很直观的就能感觉出查询接口是可以重试的,没有做幂等性的扣款接口是不能重试的。
- 业务上不能重试的接口,对于特殊的异常(其实是表示请求并没有发出去的异常),我们是可以重试的。虽然是没有做幂等性的扣款接口,但是如果抛出的是原因是 Connect Timeout 的 IOException,这样的异常代表请求还没有发出去,是可以重试的。
- 重试策略:重试几次,重试间隔。类比多处理器编程模式中的 Busy Spin 策略会造成很大的总线通量从而降低性能这个现象,如果失败立刻重试,那么在某一个实例异常导致超时的时候,会在同一时间有很多请求重试到其他实例。最好加上一定延迟。
使用 resilience4j 实现 FeignClient 重试
FeignClient 本身带重试,但是重试策略相对比较简单,同时我们还想使用断路器以及限流器还有线程隔离,resilience4j 就包含这些组件。
原理简介
Resilience4J 提供了 Retryer 重试器,官方文档地址:https://resilience4j.readme.io/docs/retry
从配置上就能理解其中的原理,但是官方文档配置并不全面,如果想看所有的配置,最好还是通过源码:
RetryConfigurationProperties.java
//重试间隔,默认 500ms
@Nullable
private Duration waitDuration;
//重试间隔时间函数,和 waitDuration 只能设置一个,默认就是 waitDuration
@Nullable
private Class<? extends IntervalBiFunction<Object>> intervalBiFunction;
//最大重试次数,包括本身那次调用
@Nullable
private Integer maxAttempts;
//通过抛出的异常判断是否重试,默认是只要有异常就会重试
@Nullable
private Class<? extends Predicate<Throwable>> retryExceptionPredicate;
//通过结果判断是否重试,默认是只要获取到结果就不重试
@Nullable
private Class<? extends Predicate<Object>> resultPredicate;
//配置抛出这些异常以及子类则会重试
@SuppressWarnings("unchecked")
@Nullable
private Class<? extends Throwable>[] retryExceptions;
//配置抛出这些异常以及子类则不会重试
@SuppressWarnings("unchecked")
@Nullable
private Class<? extends Throwable>[] ignoreExceptions;
//启用 ExponentialBackoff 延迟算法,初次重试延迟时间为 waitDuration,之后每次重试延迟时间都乘以 exponentialBackoffMultiplier,直到 exponentialMaxWaitDuration
@Nullable
private Boolean enableExponentialBackoff;
private Double exponentialBackoffMultiplier;
private Duration exponentialMaxWaitDuration;
//启用随机延迟算法,范围是 waitDuration - randomizedWaitFactor*waitDuration ~ waitDuration + randomizedWaitFactor*waitDuration
@Nullable
private Boolean enableRandomizedWait;
private Double randomizedWaitFactor;
@Nullable
private Boolean failAfterMaxAttempts;
引入 resilience4j-spring-boot2 的依赖,就可以通过 Properties 配置的方式去配置 Retryer 等所有 resilience4j 组件,例如:
application.yml
resilience4j.retry:
configs:
default:
## 最大重试次数,包括第一次调用
maxRetryAttempts: 2
## 重试等待时间
waitDuration: 500ms
## 启用随机等待时间,范围是 waitDuration - randomizedWaitFactor*waitDuration ~ waitDuration + randomizedWaitFactor*waitDuration
enableRandomizedWait: true
randomizedWaitFactor: 0.5
test-client1:
## 最大重试次数,包括第一次调用
maxRetryAttempts: 3
## 重试等待时间
waitDuration: 800ms
## 启用随机等待时间,范围是 waitDuration - randomizedWaitFactor*waitDuration ~ waitDuration + randomizedWaitFactor*waitDuration
enableRandomizedWait: true
randomizedWaitFactor: 0.5
这样,我们就可以通过如下代码,获取到配置对应的 Retryer:
@Autowired
RetryRegistry retryRegistry;
//读取 resilience4j.retry.configs.test-client1 下的配置,构建 Retry,这个 Retry 命名为 retry1
Retry retry1 = retryRegistry.retry("retry1", "test-client1");
//读取 resilience4j.retry.configs.default 下的配置,构建 Retry,这个 Retry 命名为 retry1
//不指定配置名称即使用默认的 default 下的配置
Retry retry2 = retryRegistry.retry("retry2");
引入 resilience4j-spring-cloud2 的依赖,就相当于引入了 resilience4j-spring-boot2 的依赖。并在其基础上,针对 spring-cloud-config 的动态刷新 RefreshScope 机制,增加了兼容。
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-cloud2</artifactId>
</dependency>
使用 resilience4j-feign 给 OpenFeign 添加重试
官方提供了粘合 OpenFeign 的依赖库,即 resilience4j-feign
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-feign</artifactId>
</dependency>
接下来,我们使用这个依赖,给 OpenFeign 添加重试,首先启用 OpenFeign Client 并指定默认配置:
@EnableFeignClients(value = "com.github.jojotech", defaultConfiguration = DefaultOpenFeignConfiguration.class)
在这个默认配置中,通过覆盖默认的 Feign.Builder 的方式粘合 resilience4j 添加重试:
@Bean
public Feign.Builder resilience4jFeignBuilder(
List<FeignDecoratorBuilderInterceptor> feignDecoratorBuilderInterceptors,
FeignDecorators.Builder builder
) {
feignDecoratorBuilderInterceptors.forEach(feignDecoratorBuilderInterceptor -> feignDecoratorBuilderInterceptor.intercept(builder));
return Resilience4jFeign.builder(builder.build());
}
@Bean
public FeignDecorators.Builder defaultBuilder(
Environment environment,
RetryRegistry retryRegistry
) {
String name = environment.getProperty("feign.client.name");
Retry retry = null;
try {
retry = retryRegistry.retry(name, name);
} catch (ConfigurationNotFoundException e) {
retry = retryRegistry.retry(name);
}
//覆盖其中的异常判断,只针对 feign.RetryableException 进行重试,所有需要重试的异常我们都在 DefaultErrorDecoder 以及 Resilience4jFeignClient 中封装成了 RetryableException
retry = Retry.of(name, RetryConfig.from(retry.getRetryConfig()).retryOnException(throwable -> {
return throwable instanceof feign.RetryableException;
}).build());
return FeignDecorators.builder().withRetry(
retry
);
}
微信搜索“我的编程喵”关注公众号,每日一刷,轻松提升技术,斩获各种offer:
SpringCloud升级之路2020.0.x版-30. FeignClient 实现重试的更多相关文章
- SpringCloud升级之路2020.0.x版-31. FeignClient 实现断路器以及线程隔离限流的思路
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在前面一节,我们实现了 FeignClient 粘合 resilience4j 的 Ret ...
- SpringCloud升级之路2020.0.x版-1.背景
本系列为之前系列的整理重启版,随着项目的发展以及项目中的使用,之前系列里面很多东西发生了变化,并且还有一些东西之前系列并没有提到,所以重启这个系列重新整理下,欢迎各位留言交流,谢谢!~ Spring ...
- SpringCloud升级之路2020.0.x版-41. SpringCloudGateway 基本流程讲解(1)
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 接下来,将进入我们升级之路的又一大模块,即网关模块.网关模块我们废弃了已经进入维护状态的 ...
- SpringCloud升级之路2020.0.x版-6.微服务特性相关的依赖说明
本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford spring-cl ...
- SpringCloud升级之路2020.0.x版-10.使用Log4j2以及一些核心配置
本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 我们使用 Log4 ...
- SpringCloud升级之路2020.0.x版-43.为何 SpringCloudGateway 中会有链路信息丢失
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在开始编写我们自己的日志 Filter 之前,还有一个问题我想在这里和大家分享,即在 Sp ...
- SpringCloud升级之路2020.0.x版-32. 改进负载均衡算法
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在前面一节,我们梳理了实现 Feign 断路器以及线程隔离的思路,这一节,我们先不看如何源 ...
- SpringCloud升级之路2020.0.x版-42.SpringCloudGateway 现有的可供分析的请求日志以及缺陷
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 网关由于是所有外部用户请求的入口,记录这些请求中我们需要的元素,对于线上监控以及业务问题定 ...
- SpringCloud升级之路2020.0.x版-29.Spring Cloud OpenFeign 的解析(1)
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在使用云原生的很多微服务中,比较小规模的可能直接依靠云服务中的负载均衡器进行内部域名与服务 ...
随机推荐
- JDBC 基础入门
由于我也是初学参考的是网上的或者是培训机构的资料所以可能会有错误的信息,仅供参考 一.什么是JDBC(Java Data Base Connectivity)? java程序连接数据库,JDBC是由S ...
- python多线程与_thread模块
进程与线程 1.进程:计算机程序只是存储在磁盘中的可执行二进制(或其他类型)的文件.只有把他们加载到内存中并被操作系统调用,才具有其生命周期.进程则是一个执行中的程序.每个进程都拥有自己的地址空间,内 ...
- 『GoLang』面向对象
我们总结一下前面看到的:Go 没有类,而是松耦合的类型.方法对接口的实现. 面向对象语言最重要的三个方面分别是:封装,继承和多态,在 Go 中它们是怎样表现的呢? Go实现面向对象的两个关键是stru ...
- 使用three.js实现炫酷的酸性风格3D页面
背景 近期学习了 WebGL 和 Three.js 的一些基础知识,于是想结合最近流行的酸性设计风格,装饰一下个人主页,同时总结一些学到的知识.本文内容主要介绍,通过使用 React + three. ...
- Vue组件间的数据传输
1.父组件向子组件传输数据:自定义属性 1 //父组件 2 <Son :msg="message" :user="userinfo"></So ...
- Unity——资源文件夹介绍
Unity资源文件夹介绍 1.编辑时 在Asset文件下存在Resources和SteamingAsset文件夹: Resources 只读不可修改,打包时直接写死,没有办法通过热更新替换资源: 可以 ...
- 10 月 30 日 北京 LiveVideoStack 阿里云视频云专场限量赠票 100 张
10 月 30 日 | 北京 LiveVideoStack 将携手阿里云共邀 4 位技术大咖,一同探讨从上云到创新,视频云的新技术与新场景.阿里云视频云依托阿里云服务数百万开发者的卓越服务能力与实践, ...
- FastAPI 学习之路(九)请求体有多个参数如何处理?
系列文章: FastAPI 学习之路(一)fastapi--高性能web开发框架 FastAPI 学习之路(二) FastAPI 学习之路(三) FastAPI 学习之路(四) FastAPI 学习之 ...
- 初探webpack之从零搭建Vue开发环境
初探webpack之搭建Vue开发环境 平时我们可以用vue-cli很方便地搭建Vue的开发环境,vue-cli确实是个好东西,让我们不需要关心webpack等一些繁杂的配置,然后直接开始写业务代码, ...
- C++ 与 Visual Studio 2019 和 WSL(四)——库组件
C++ 与 Visual Studio 2019 和 WSL (库组件) Reference 在 C++ 项目中使用库和组件 | Microsoft Docs 演练:创建和使用自己的动态链接库 (C+ ...