一、簡介
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機制生效)。