一、简介
ribbon的重试是指consumer调用provider发生异常后,ribbon发起重试。
RestTemplate和Feign使用的重试机制不一样,下面分开进行说明。
二、RestTemplate重试
RestTemplate都是spring-cloud来做的,内部依赖于spring-retry,增加如下配置打开重试机制:
pom
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency>
配置文件:
spring: cloud: loadbalancer: retry: enabled: true
实现 参考结合ribbon中,与注入LoadBalancerInterceptor类似:spring-cloud-commons/META-INF/spring.factories中的LoadBalancerAutoConfiguration,如下条件时,它会被启用:
@Configuration @ConditionalOnClass(RestTemplate.class) @ConditionalOnBean(LoadBalancerClient.class) @EnableConfigurationProperties(LoadBalancerRetryProperties.class)
启动后会注入RetryLoadBalancerInterceptor,也就是说,同样使用拦截器实现重试。
RetryLoadBalancerInterceptor内部会使用RetryTemplate发起重试,重试策略为InterceptorRetryPolicy,具体的策略为LoadBalancedRetryPolicy。
配置 重试相关的配置如下:(这些参数在结合ribbon也有相关说明)
- MaxAutoRetries,默认值为0 同一个server上的重试次数(如果第一次调用就是该server,则不包括该次调用) 即如果设置为1,会在失败的server上再发起一次调用(建议保持默认值0,因为通常情况下,该server调用失败,立马再发起第二次调用通常也是失败的)
- OkToRetryOnAllOperations,默认值为false 是否在所有类型的请求上都重试,默认只get请求才会重试
- MaxAutoRetriesNextServer,默认值为1 换下一个实例进行重试的次数,1说明下一个server上只调用一次,无论成功失败不再重试。
- 所以MaxAutoRetries=0和MaxAutoRetriesNextServer=1的意思就是,第一次如果调用失败,则换下一个server重新调用。总过发生两次调用。
三、Feign重试
根据spring-cloud-netflix-core/META-INF/spring.factories的配置:FeignRibbonClientAutoConfiguration,当pom中包含spring-retry时,feign自动开启重试机制。
实现 重试实现的主要类为RetryableFeignLoadBalancer,其使用的retry策略为FeignRetryPolicy,由于它继承自LoadBalancedRetryPolicy,所以其他重试的策略与LoadBalancedRetryPolicy一致。
配置 除了RestTemplate中介绍的三个配置(MaxAutoRetries,OkToRetryOnAllOperations,MaxAutoRetriesNextServer)外,还有额外两个配置:
ConnectTimeout
feign发送http请求链接超时
ReadTimeout
feign http请求远程交互时,读取超时
有两种方法可以设置该配置:
通过配置文件:
xxx-service: ribbon: ConnectTimeout: 2000 ReadTimeout: 1000
通过类配置:
@FeignClient(configuration = FeignConfig.class) @Configuration public class FeignConfig { public static int connectTimeOutMillis = 2000; public static int readTimeOutMillis = 500; @Bean public Request.Options options() { return new Request.Options(connectTimeOutMillis, readTimeOutMillis); } }
其中类配置的优先级高于配置文件配置。
需要说明一点: 由于feign采用动态代理注入代理类(SynchronousMethodHandler),其自身包含重试逻辑:
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template);
} catch (RetryableException e) {
retryer.continueOrPropagate(e);
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
默认情况下feign自身的retry功能是关闭的,如果发现重试情况,最好关闭feign自身的重试功能,Retryer.NEVER_RETRY表示不重试。
默认feign还是使用Ribbon的LoadBalancerCommand进行重试
但是官方已经建议使用spring的RetryTemplate统一支持重试:https://github.com/spring-cloud/spring-cloud-netflix/issues/1295
相关分析已经整理到另外一篇文章FeignClient
四、zuul重试
这里由于zuul重试其实也是依赖的ribbon,故一起整理出来
由于重试机制在spring-cloud里比较乱,主要有如下两种
- ribbon自身的重试(LoadBalancerCommand.submit()方法)
- spring-retry实现的重试(参见与springcloud结合中Retry开头的类)
而zuul这两种方式都支持,经过测试debug sping-cloud的Dalston.SR3版本,得出如下结论(以下配置都依赖了okhttp作为client):
依赖spring-retry
pom中配置:
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency>
application.yml中配置:
zuul: retryable: true
rebbion配置(非必须)
ribbon: okhttp: enabled: true ReadTimeout: 1000 ConnectTimeout: 2000 MaxAutoRetriesNextServer: 1 MaxAutoRetries: 0
那么此时zuul将会使用RetryableOkHttpLoadBalancingClient做retry,ribbon自身的retry将被禁用。
不依赖spring-retry,那么会使用ribbon的retry机制,这个retry机制默认是开启的。也就是说并不用配置zuul.retryable=true
上述说的是否使用ribbon的retry其实是下面的方法,如果依赖了spring-retry,注入的RetryableOkHttpLoadBalancingClient会覆盖默认的retry处理方法(不进行ribbon的retry):
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(OkHttpRibbonRequest request, IClientConfig requestConfig) {
return new RequestSpecificRetryHandler(false, false, RetryHandler.DEFAULT, null);
}
如果不依赖spring-retry,默认注入的是OkHttpLoadBalancingClient,那么retry处理方法为父类AbstractLoadBalancingClient的:
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(
final S request, final IClientConfig requestConfig) {
if (this.okToRetryOnAllOperations) {
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
requestConfig);
}
if (!request.getContext().getMethod().equals("GET")) {
return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(),
requestConfig);
}
else {
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
requestConfig);
}
}
它会启用ribbon的retry(即LoadBalancerCommand.submit()中的retry机制生效)。