10.重試

tmp.md

一、簡介

ribbon的重試是指consumer調用provider發生異常後,ribbon發起重試。

RestTemplate和Feign使用的重試機制不一樣,下面分開進行說明。

二、RestTemplate重試

RestTemplate都是spring-cloud來做的,內部依賴於spring-retry,增加如下配置打開重試機制:

  1. pom

    <dependency>
     <groupId>org.springframework.retry</groupId>
     <artifactId>spring-retry</artifactId>
    </dependency>
    
  2. 配置文件:

    spring: 
      cloud: 
        loadbalancer:
          retry: 
            enabled: true
    
  3. 實現 參考結合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。

  4. 配置 重試相關的配置如下:(這些參數在結合ribbon也有相關說明)

    1. MaxAutoRetries,默認值爲0 同一個server上的重試次數(如果第一次調用就是該server,則不包括該次調用) 即如果設置爲1,會在失敗的server上再發起一次調用(建議保持默認值0,因爲通常情況下,該server調用失敗,立馬再發起第二次調用通常也是失敗的)
    2. OkToRetryOnAllOperations,默認值爲false 是否在所有類型的請求上都重試,默認只get請求才會重試
    3. MaxAutoRetriesNextServer,默認值爲1 換下一個實例進行重試的次數,1說明下一個server上只調用一次,無論成功失敗不再重試。
    4. 所以MaxAutoRetries=0和MaxAutoRetriesNextServer=1的意思就是,第一次如果調用失敗,則換下一個server重新調用。總過發生兩次調用。

三、Feign重試

根據spring-cloud-netflix-core/META-INF/spring.factories的配置:FeignRibbonClientAutoConfiguration,當pom中包含spring-retry時,feign自動開啓重試機制。

  1. 實現 重試實現的主要類爲RetryableFeignLoadBalancer,其使用的retry策略爲FeignRetryPolicy,由於它繼承自LoadBalancedRetryPolicy,所以其他重試的策略與LoadBalancedRetryPolicy一致。

  2. 配置 除了RestTemplate中介紹的三個配置(MaxAutoRetries,OkToRetryOnAllOperations,MaxAutoRetriesNextServer)外,還有額外兩個配置:

    1. ConnectTimeout

      feign發送http請求鏈接超時

    2. ReadTimeout

      feign http請求遠程交互時,讀取超時

    3. 有兩種方法可以設置該配置:

      1. 通過配置文件:

        xxx-service: 
          ribbon: 
          ConnectTimeout: 2000
          ReadTimeout: 1000     
        
      2. 通過類配置:

        @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裏比較亂,主要有如下兩種

    1. ribbon自身的重試(LoadBalancerCommand.submit()方法)
    2. spring-retry實現的重試(參見與springcloud結合中Retry開頭的類)

    而zuul這兩種方式都支持,經過測試debug sping-cloud的Dalston.SR3版本,得出如下結論(以下配置都依賴了okhttp作爲client):

    1. 依賴spring-retry

      1. pom中配置:

         <dependency>
          <groupId>org.springframework.retry</groupId>
          <artifactId>spring-retry</artifactId>
         </dependency>
        
      2. application.yml中配置:

        zuul:
           retryable: true
        
      3. 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機制生效)。

  • 發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章