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
方法.
RetryableFeignLoadBalancer
的executeWithLoadBalancer
方法是在父类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
,并用FeignRetryPolicy
将 RibbonLoadBalancedRetryPolicy
适配成一个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
,否则会导致接口还没重试完就直接熔断了