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机制生效)。

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