如题,本文基于Spring Cloud Finchley.SR2

OpenFeign的重试

OpenFeign配置重试后,逻辑分析

对比Daltson和Finchley的基本组件,发现Ribbon还有Hystrix的重试逻辑基本没变,feign编程openfeign之后,增加了个重试逻辑,我们用下面这个图来展示其中的逻辑:

首先搞清楚调用链:

可以总结如下:

  1. OpenFeign有自己的重试机制,重试的是整个后面的调用栈(也就是说,ribbon的重试又被整个重新重试了一遍)
  2. Ribbon通过增加Spring-retry还有相关配置开启了重试,这个重试机制对于OpenFeign是不起作用的,但是对于@LoadBalanced注解修饰的RestTemplate是有作用的。
  3. RetryableFeignLoadBalancer使用RetryTemplate实现了自己的重试逻辑,其中的RetryPolicy还是RibbonLoadBalancedRetryPolicy,读取的配置还是ribbon.MaxAutoRetriesribbon.MaxAutoRetriesNextServer,所以其实这两个配置是在这里起作用了。

我们来看下代码实现:
首先是Ribbon的重试(LoadBalancerCommand)

public Observable<T> submit(final ServerOperation<T> operation) {//省略无关紧要的代码//获取配置:ribbon.MaxAutoRetries和ribbon.MaxAutoRetriesNextServer//每台服务器最多重试次数,但是首次调用不包括在内final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer();//最多重试多少台服务器,但是首次调用不包括在内final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer();// Use the load balancerObservable<T> o = //通过负载均衡器获取一个Server执行请求(server == null ? selectServer() : Observable.just(server)).concatMap(new Func1<Server, Observable<T>>() {@Override// Called for each server being selectedpublic Observable<T> call(Server server) {context.setServer(server);final ServerStats stats = loadBalancerContext.getServerStats(server);//对于每次重试,都要走的逻辑Observable<T> o = Observable.just(server).concatMap(new Func1<Server, Observable<T>>() {@Overridepublic Observable<T> call(final Server server) {context.incAttemptCount();//省略无关代码//operation.call(server)就是调用RetryableFeignLoadBalancer的execute方法//但外层有封装方法把它返回的结果封装成了rxjava的Observable//这里针对这个Observable增加回调//这些回调其实就是记录一些调用数据,用于负载均衡规则选择serverreturn operation.call(server).doOnEach(new Observer<T>() {//省略实现});}});if (maxRetrysSame > 0) o = o.retry(retryPolicy(maxRetrysSame, true));return o;}});//补充对于尝试下一个server的逻辑    if (maxRetrysNext > 0 && server == null) o = o.retry(retryPolicy(maxRetrysNext, false));//在有异常的时候,判断是否超过重试次数return o.onErrorResumeNext(new Func1<Throwable, Observable<T>>() {@Overridepublic Observable<T> call(Throwable e) {if (context.getAttemptCount() > 0) {if (maxRetrysNext > 0 && context.getServerAttemptCount() == (maxRetrysNext + 1)) {e = new ClientException(ClientException.ErrorType.NUMBEROF_RETRIES_NEXTSERVER_EXCEEDED,"Number of retries on next server exceeded max " + maxRetrysNext+ " retries, while making a call for: " + context.getServer(), e);}else if (maxRetrysSame > 0 && context.getAttemptCount() == (maxRetrysSame + 1)) {e = new ClientException(ClientException.ErrorType.NUMBEROF_RETRIES_EXEEDED,"Number of retries exceeded max " + maxRetrysSame+ " retries, while making a call for: " + context.getServer(), e);}}if (listenerInvoker != null) {listenerInvoker.onExecutionFailed(e, context.toFinalExecutionInfo());}return Observable.error(e);}});
}

可以看出,必须调用的operation.call(server)有异常走到ERROR处理逻辑才会走这里的重试。但是我们看RetryableFeignLoadBalancer的源代码可以发现,RetryableFeignLoadBalancer用的RetryTemplate实现了自己的重试,根本不会将异常抛出来到外层。

然后是OpenFeign的执行(RetryableFeignLoadBalancer):

public RibbonResponse execute(final RibbonRequest request, IClientConfig configOverride) throws IOException {//省略无关代码//读取ribbon.MaxAutoRetries和ribbon.MaxAutoRetriesNextServer生成RetryPolicy用于之后的RetryTemplate重试final LoadBalancedRetryPolicy retryPolicy = this.loadBalancedRetryFactory.createRetryPolicy(this.getClientName(), this);RetryTemplate retryTemplate = new RetryTemplate();BackOffPolicy backOffPolicy = this.loadBalancedRetryFactory.createBackOffPolicy(this.getClientName());retryTemplate.setBackOffPolicy((BackOffPolicy)(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy));RetryListener[] retryListeners = this.loadBalancedRetryFactory.createRetryListeners(this.getClientName());if (retryListeners != null && retryListeners.length != 0) {retryTemplate.setListeners(retryListeners);}retryTemplate.setRetryPolicy((RetryPolicy)(retryPolicy == null ? new NeverRetryPolicy() : new FeignRetryPolicy(request.toHttpRequest(), retryPolicy, this, this.getClientName())));return (RibbonResponse)retryTemplate.execute(new RetryCallback<RibbonResponse, IOException>() {public RibbonResponse doWithRetry(RetryContext retryContext) throws IOException {Request feignRequest = null;if (retryContext instanceof LoadBalancedRetryContext) {ServiceInstance service = ((LoadBalancedRetryContext)retryContext).getServiceInstance();if (service != null) {feignRequest = ((RibbonRequest)request.replaceUri(RetryableFeignLoadBalancer.this.reconstructURIWithServer(new Server(service.getHost(), service.getPort()), request.getUri()))).toRequest();}}if (feignRequest == null) {feignRequest = request.toRequest();}Response response = request.client().execute(feignRequest, options);//判断ribbon.retryableStatusCodes的状态码是否包含返回码,如果包含则抛出异常//不包含就返回封装的response,抛出异常会直接根据RetryPolicy进行重试//这里的RetryPolicy就是之前说的RibbonLoadBalancedRetryPolicyif (retryPolicy.retryableStatusCode(response.status())) {byte[] byteArray = response.body() == null ? new byte[0] : StreamUtils.copyToByteArray(response.body().asInputStream());response.close();throw new RibbonResponseStatusCodeException(RetryableFeignLoadBalancer.this.clientName, response, byteArray, request.getUri());} else {return new RibbonResponse(request.getUri(), response);}}}, new LoadBalancedRecoveryCallback<RibbonResponse, Response>() {protected RibbonResponse createResponse(Response response, URI uri) {return new RibbonResponse(uri, response);}});
}

最后OpenFeign的Retryer重试在哪里执行呢?就是在拿到Response之后,判断Response的header里面是否有Retry-After这个Header,如果有,就按照Retryer的配置进行重试,这个重试会重新调用整个调用栈进行重试(源代码略,参考feign.SynchronousMethodHandlerfeign.codec.ErrorDecoder

配置总结与目前的缺陷

目前实现的配置是,本机不重试,最多重试另一台机器,只对GET请求的返回码为500的请求重试,不考虑Retry-After这个Header

maven依赖(除了SpringCloud基本依赖):

<dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</artifactId><version>1.2.4.RELEASE</version>
</dependency>

application.properties配置:

#开启hystrix
feign.hystrix.enabled=true
#关闭断路器
hystrix.command.default.circuitBreaker.enabled=false
#禁用hystrix远程调用超时时间
hystrix.command.default.execution.timeout.enabled=false
hystrix.threadpool.default.coreSize=50
#ribbon连接超时
ribbon.ConnectTimeout=500
#ribbon读超时
ribbon.ReadTimeout=8000
#最多重试多少台服务器,但是首次调用不包括在内
ribbon.MaxAutoRetriesNextServer=1
#每台服务器最多重试次数,但是首次调用不包括在内
ribbon.MaxAutoRetries=0
#需要重试的状态码
ribbon.retryableStatusCodes=500

可能存在的缺陷:

  1. 对于默认的负载均衡规则基于RoundRobin,目前的代码,如果ribbon.MaxAutoRetries=0ribbon.MaxAutoRetriesNextServer=1,也会调用两次获取Server,这样如果集群正好只有两个,客户端只有一个,可能会出现一直重试调用同一台机器的情况,所以,负载均衡规则最好选择com.netflix.loadbalancer.AvailabilityFilteringRule,对于这个Rule,我另一篇文章做了分析,加上配置:
你的微服务名.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.AvailabilityFilteringRule# 单实例最大活跃链接个数
niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit=50#eureka客户端ribbon刷新时间
#默认30s
ribbon.ServerListRefreshInterval=1000# ribbon.ServerListRefreshInterval时间内有多少断路次数就触发断路机制(以下配置都是默认值,可以不配置,这里只是为了说明)
niws.loadbalancer.你的微服务名.connectionFailureCountThreshold=3
niws.loadbalancer.你的微服务名.circuitTripTimeoutFactorSeconds=10
niws.loadbalancer.你的微服务名.circuitTripMaxTimeoutSeconds=30

这样就算一直重试同一台,也会让这台机器快速断路(一种是连接不上抛出SocketException或者是调用超时SocketTimeoutException,还有就是活跃请求过多)。

  1. 对于非微服务不可用,而是共用的某个模块不可用,例如数据库,这么做重试可能会导致雪崩现象的出现,例如某个接口逻辑是:
1. 调用另一个重量级操作
2. 读取数据库
3. 返回拼装结果

所以业务上最好设计时,先读取公共模块,之后再做重量级操作。技术上避免,就是通过Openfeign的Retryer的重试实现,如果业务上发现数据库超时或者数据库连接不上的异常,就返回503并且填充Retry-After这个Header,让Openfeign的Retryer过一会再重试
更好的方案是按照阿里重试方案,1s,2s,4s,8s之后这样阶梯式重试

Spring Cloud Finchley OpenFeign的重试配置相关的坑相关推荐

  1. spring cloud整合OpenFeign

    spring cloud整合OpenFeign pom.xml配置 <!-- https://mvnrepository.com/artifact/org.springframework.clo ...

  2. 在运行时在Spring Cloud Config中刷新属性配置

    在本系列Spring Cloud Config的教程系列中,我们将讨论在运行时刷新属性配置的过程,我们将使用Spring Boot致动器/refresh端点进行/refresh . 此外,我们还将研究 ...

  3. Spring Cloud整合Nacos实现动态配置

    前提 已经安装并启动了nacos-server服务端. 整合 创建一个maven工程并引入以下依赖: <dependency><groupId>org.springframew ...

  4. nacos 配置动态刷新_使用 Spring Cloud Alibaba Nacos Config 作为配置中心

    什么是 Nacos Config 在分布式系统中,由于服务数量巨多,为了方便服务 配置文件统一管理,实时更新,所以需要分布式配置中心组件. Spring Cloud Alibaba Nacos Con ...

  5. Spring Cloud第六章:配置中心Config

    在上一篇文章讲述zuul的时候,已经提到过,使用配置服务来保存各个服务的配置文件.它就是Spring Cloud Config. 一.简介 在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管 ...

  6. Spring Cloud Alibaba - 19 Nacos Config配置中心加载不同微服务的通用配置的两种方式

    文章目录 Pre 实现 方式一 通过 shared-dataids 方式 方式二 通过 ext-config方式 配置文件优先级 源码 Pre Spring Cloud Alibaba - 18 Na ...

  7. Spring Cloud Alibaba - 18 Nacos Config配置中心加载相同微服务的不同环境下的通用配置

    文章目录 需求 实现 Step 1 Nacos Config 新增公共配置 Step 2 验证 配置文件优先级 源码 需求 举个例子,同一个微服务,通常我们的servlet-context 都是相同的 ...

  8. Spring Cloud Stream多RabbitMQ实例配置时报错no default binder has been set

    当前Spring Cloud Rabbit的版本为2.1.2 <dependency><groupId>org.springframework.cloud</groupI ...

  9. Spring Cloud(七):配置中心

    Spring Cloud Config项目是一个解决分布式系统的配置管理方案.它包含了Client和Server两个部分,server提供配置文件的存储.以接口的形式将配置文件的内容提供出去,clie ...

最新文章

  1. java字典序列化_Java对象序列化,Serialize Java Data Object,音标,读音,翻译,英文例句,英语词典...
  2. .Net开发中的多线程编程总结
  3. python telnetlib 协商_Python telnetlib:令人惊讶的问题
  4. 首付贷换了马甲,又重现江湖了
  5. [转]cocos2d游戏开发,常用工具集合
  6. java编程中的持有对方引用是什么意思?有什么作用?
  7. java 二分查找_JAVA 实现二分查找算法。我知道你会,但没你想象的那么简单
  8. 文本生成解码策略笔记-常见解码策略
  9. rabbitmq接收不到消息_SpringBoot2.x系列教程63--SpringBoot整合消息队列之RabbitMQ详解
  10. poj 3461 Oulipo kmp 预处理
  11. h3c交换机-初级命令
  12. Python数据分析与应用 ---- 航空公司客户价值分析
  13. 联想第一季度业绩超预期,增长势头强劲
  14. 【JS】学习记录【页面打印】
  15. 笔记本外接显示器闪烁问题
  16. 20万、50万、100万年薪的算法工程师能力上有哪些差距?
  17. mysql comment
  18. Moz-css 大全
  19. 基于B/S的超市收银系统
  20. Loser应该知道的6个残酷人生事实(血泪翻译)

热门文章

  1. 史上最全教程没有之一,微信小程序使用云开发解决微信支付问题,我走了几天几夜的弯路啊
  2. 001 变量与数据类型
  3. 梦幻西游手游排队显示服务器已满,梦幻西游手游排队进不去 一直排队解决方法...
  4. LaTeX命令速查手册 - 方法总比问题多
  5. 【其他】对数转换的作用
  6. 小孔成像总结_相机标定是怎么回事——相机成像数学模型
  7. 2022年中国研究生数学建模竞赛E题思路及参考代码-草原放牧策略研究
  8. 关于axios配置拦截器不生效的问题
  9. LIB,DLL区别 及 QT中如何添加LIB,DLL
  10. 使用XXLjob中间件进行定时任务的管理