feign、ribbion,这两个组件是spring cloud 远程调用最常用的两个组件。这两个组件分别有各自的重试机制和默认配置。
如下的示例值是ribbon的默认配置,指定ribbon服务的配置会覆盖全局ribbon的配置。

# 对所有的服务该配置都生效
ribbon:
  ConnectTimeout: 1000
  ReadTimeout: 1000
  OkToRetryOnAllOperations: false
  MaxAutoRetries: 0
  MaxAutoRetriesNextServer: 1
  retryableStatusCodes: ''
#只针对  sevice-x生效
sevice-x:
  ribbon:
    ConnectTimeout: 1000
    ReadTimeout: 1000
    OkToRetryOnAllOperations: false
    MaxAutoRetries: 0
    MaxAutoRetriesNextServer: 1
    retryableStatusCodes: ''

1. feign重试

feign重试定义了一个接口feign.Retryer,当发生可重试的异常时,则循环执行feign.Retryer#continueOrPropagate方法。如果continueOrPropagate抛出异常则退出while循环,若continueOrPropagate没有抛出异常则继续重试。
在这里插入图片描述

1) feign硬编码的默认重试

feign.Retryer有个默认实现类 feign.Retryer.Default,其无参构造方法会构建一个间隔100毫秒、最多重试5次的重试器Retryer.
在这里插入图片描述
feign.Retryer.Default无参构造方法创建的对象作为feign.Feign.Builder#retryer的默认值。
所以在使用FeignInterfaceX target = Feign.builder().target(FeignInterfaceX.class, "http://xxx:port/xxx_path")这类硬编码创建的Feign Client的默认重试器就是默认值new Retryer.Default()(间隔100毫秒、最多重试5次)
在这里插入图片描述

2) spring环境中feign的默认重试

在spring环境中feign client是被org.springframework.cloud.openfeign.FeignClientFactoryBean这个工厂本创建出来的
feign的重试配置的主逻辑在方法org.springframework.cloud.openfeign.FeignClientFactoryBean#configureUsingConfiguration中。此方法properties.isDefaultToProperties()的默认值是true,我们一般也不会去改它,所以一般是执行红方框中的逻辑:(1)先执行全局配置configureUsingConfiguration(context, builder),(2)再执行属性配置类中FeignClientProperties的默认配置, (3)最后执行属性配置类FeignClientProperties中当前feign client特定的配置。这三个步骤的覆盖顺序是后者覆盖前者,也就是越往后优先级越高。默认情况下我们未做任何配置时,FeignClientProperties是空对象,也就是说此时只会执行步骤(1)的属性配置。
在这里插入图片描述

全局属性配置方法org.springframework.cloud.openfeign.FeignClientFactoryBean#configureUsingConfiguration回到spring容器中去取重试器对象feign.Retryer
在这里插入图片描述

org.springframework.cloud.openfeign.FeignClientsConfiguration#feignRetryer配置类方法配置一个Retryer Bean对象,所以上面代码中的getInheritedAwareOptional(context, Retryer.class)获取到的重试器是Retryer.NEVER_RETRY

在这里插入图片描述

Retryer.NEVER_RETRY没有有意义的实现逻辑,接口continueOrPropagate直接抛出异常。
也就是在spring 环境中feign本身的默认重试逻辑是不重试、直接抛出异常。
在这里插入图片描述

2. ribbon重试

真实环境中,一般是将feign、ribbon集成起来结合使用。
在feign引入ribbon负载均衡时,远程调用的feign client会用LoadBalancerFeignClient.
LoadBalancerFeignClient.execute方法中lbClient的代码行会创建一个可重试的FeignClient RetryableFeignLoadBalancer
在这里插入图片描述

在这里插入图片描述

现在我们重点关注下RetryableFeignLoadBalancer.
RetryableFeignLoadBalancer的构造方法会创建一个默认的重试处理器DefaultLoadBalancerRetryHandler 这个处理器很重要
在这里插入图片描述
DefaultLoadBalancerRetryHandler的构造方法从IClientConfig中取出了配置的MaxAutoRetries MaxAutoRetriesNextServer OkToRetryOnAllOperations 这几个属性。
MaxAutoRetries:表示单实例重试的最大次数,其默认值DefaultClientConfigImpl.DEFAULT_MAX_AUTO_RETRIES是0。
MaxAutoRetriesNextServer :表示重试的最大实例数,其默认值DefaultClientConfigImpl.DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER是1。
OkToRetryOnAllOperations :表示是否对说有http请求类型都进行重试,默认值是false,即只对http接口的GET请求重试(GET请求一般是读操作,多次读不影响正常的业务逻辑,而POST 、PUT等请求一般是写操作,多次写可能会产生脏数据,如果要将此参数配置为true,请自行做好接口幂等性)
在这里插入图片描述

现在回到上边LoadBalancerFeignClient.execute的代码片段
return lbClient(clientName) .executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();中的executeWithLoadBalancer方法.
RetryableFeignLoadBalancerexecuteWithLoadBalancer方法是在父类AbstractLoadBalancerAwareClient中实现的。
在这里插入图片描述
buildLoadBalancerCommand方法会获取一个RequestSpecificRetryHandler
在这里插入图片描述
AbstractLoadBalancerAwareClient#getRequestSpecificRetryHandler抽象方法是在RetryableFeignLoadBalancer自己本身类定义中实现的。此方法返回了RequestSpecificRetryHandler包装过的DefaultLoadBalancerRetryHandler(this.getRetryHandler()是前面提到的RetryableFeignLoadBalancer构造方法中创建的DefaultLoadBalancerRetryHandler)
getRequestSpecificRetryHandler方法创建RequestSpecificRetryHandler时,前两个构造方法参数都硬编码为false, 这会导致RequestSpecificRetryHandler几乎没有任何重试功能。
在这里插入图片描述

RetryHandler. isRetriableException接口方法是用来判断发生某种异常时接口是否重试,而这里的RequestSpecificRetryHandler对这接口方法的实现是:几乎始终返回false。这个重试处理器没啥实际用处。
在这里插入图片描述
现在回到上面的RetryableFeignLoadBalancer.executeWithLoadBalancer方法中的 command.submit( new ServerOperation<T>() ....)代码片段,这行代码利用RxJava提交了一个异步任务,LoadBalancerCommand#submit方法实现很长,其中有大量的回调方法,这里就不展开讲了。这个command.submit是利用LoadBalancerCommand实现重试。但因为我们在引入了Spring Retry框架,导致我们构建出的retryHandler是个上面提到的没有重试功能的
RequestSpecificRetryHandler,真正的重试逻辑在spring retry RetryTemplate中。
在这里插入图片描述
上面红框中的代码是去执行RetryableFeignLoadBalancer#execute方法,此方法主要是给RetryTemplate构建了一个重试策略 RibbonLoadBalancedRetryPolicy,并用FeignRetryPolicyRibbonLoadBalancedRetryPolicy适配成一个spring retry 的RetryPolicy,然后执行org.springframework.retry.support.RetryTemplate#execute()进行接口调用和重试。
在这里插入图片描述
RibbonLoadBalancedRetryPolicy是ribbion重试的很重要的一个类,下面他的这个构造方法会从IClientConfig中获取retryableStatusCodes属性(此参数可在配置文件中配置’ribbon.retryableStatusCodes’或’service-x.ribbon.retryableStatusCodes’,默认值是空字符串,多值http status时可用逗号隔开),这个属性是表示http状态行在这些指定范围内就可重试。
在这里插入图片描述
下面关注这几个重试判断的方法
RibbonLoadBalancedRetryPolicy.canRetry(LoadBalancedRetryContext):根据http Method类型判断,lbContext.isOkToRetryOnAllOperations()返回值是IClientConfig中的OkToRetryOnAllOperations属性,默认值是false,也就是说只有GET方法才会canRetry才会返回true.
RibbonLoadBalancedRetryPolicy.canRetrySameServer(LoadBalancedRetryContext): 当前服务实例是否可以重试,lbContext.getRetryHandler().getMaxRetriesOnSameServer()返回值来源于IClientConfig中的MaxAutoRetries,默认值是0。当在当前服务实例重试次数小于MaxAutoRetries且次http method 支持重试时,可以继续重试。
RibbonLoadBalancedRetryPolicy.canRetryNextServer(LoadBalancedRetryContext): 是否还可以重试下一个服务实例,lbContext.getRetryHandler().getMaxRetriesOnNextServer()返回值来源于IClientConfig中的MaxAutoRetriesNextServer,默认值是1。当已重试的服务实例个数小于MaxAutoRetriesNextServer且此http method 支持重试时,可以继续重试。
RibbonLoadBalancedRetryPolicy.registerThrowable:统计异常,记录重试次数。
在这里插入图片描述

上面的几个方法块中都调用LoadBalancerContext相关方法,现在来研究下它。上面的lbContext实际类型是RibbonLoadBalancerContext,但主要实现逻辑在父类LoadBalancerContext中。
在这里插入图片描述

RetryTemplate.execute的底层调用函数是doExecute,其核心逻辑还是比较清晰的。目标函数执行失败,在重试次数未用尽、且可以重试时就在while循环中反复调用执行目标函数,在最终的重试失败退出时执行recovery函数。

在这里插入图片描述

4 总结

如果某服务的所有实例的相应接口都不可用,那么此接口的的总调用次数是tCount=(MaxAutoRetries+1)*(MaxAutoRetriesNextServer+1),重试次数是tCount-1,接口的总超时时间是tTime=(MaxAutoRetries+1)*(MaxAutoRetriesNextServer+1)*(ConnectTimeout+ReadTimeout),所以hystrix的超时时间应该大于这个tTime,否则会导致接口还没重试完就直接熔断了

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐