feign和ribbon的重试机制

feign和ribbon的重试机制

前言

首先声明一点,这里的重试并不是报错以后的重试,而是负载均衡客户端发现远程请求实例不可到达后,去重试其他实例。

feign重试

feign的重试机制默认是关闭的,源码如下

    //FeignClientsConfiguration.java
    @Bean
    @ConditionalOnMissingBean
    public Retryer feignRetryer() {
        return Retryer.NEVER_RETRY;
    }

当没有spring容器中不存在retryer这个实例的时候,会初始化这个bean, NEVER_RETRY

如何开启

    @Bean
    public Retryer feignRetryer() {
        return new Retryer.Default();
    }

在你的配置类中,添加如上代码,当然你也可以自定义Retryer。 默认重试5次

源码实现

代码入口: feign.SynchronousMethodHandler

  @Override
  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进行重试
        retryer.continueOrPropagate(e);
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }

步骤说明:

1.获取请求模板

2.获取重试对象

3.开启一个死循环,执行请求,如果出现异常,则被retryer处理。这个时候就要看retryer的具体实例了,如果是Retryer.NEVER_RETRY 可以看一下他的实现

Retryer NEVER_RETRY = new Retryer() {

    @Override
    public void continueOrPropagate(RetryableException e) {
      // 直接抛了个异常
      throw e;
    }

    @Override
    public Retryer clone() {
      return this;
    }
  };

看一下Retryer.Default的实现

public void continueOrPropagate(RetryableException e) {
      // maxAttempts =5 , 如果重试次数大于5了,则直接抛个 异常出去
      if (attempt++ >= maxAttempts) {
        throw e;
      }

      long interval;
      if (e.retryAfter() != null) {
        interval = e.retryAfter().getTime() - currentTimeMillis();
        if (interval > maxPeriod) {
          interval = maxPeriod;
        }
        if (interval < 0) {
          return;
        }
      } else {
        interval = nextMaxInterval();
      }
      try {
        Thread.sleep(interval);
      } catch (InterruptedException ignored) {
        Thread.currentThread().interrupt();
      }
      sleptForMillis += interval;
    }

综上所述,上面那个while循环什么情况下会直接退出?

1.重试对象里面抛了个异常出来,while循环退出。

2.执行请求成功,直接return出去

3.开启了hystrix,hystrix超时,则直接熔断

ribbon的重试机制

ribbon的重试机制是默认重试一次。

属性 备注
ribbon.MaxAutoRetrues 重试相同的服务,默认次数为1
ribbon.MaxAutoRetruesNextServer 重试下一台服务,默认为1
ribbon.connectTimeout 连接超时时间
ribbon.readTimeout 读取数据超时
ribbon.okToRetryOnAllOperations 无论是超时还是connet异常,统统重试,默认为false,

源码实现

代码入口:AbstractLoadBalancerAwareClient

public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
        // 获取请求重试handler
        RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, requestConfig);
        // 构建ribbon的负载均衡请求
        LoadBalancerCommand<T> command = LoadBalancerCommand.<T>builder()
                .withLoadBalancerContext(this)
                .withRetryHandler(handler)
                .withLoadBalancerURI(request.getUri())
                .build();

        try {
            // 执行请求
            return command.submit(
                new ServerOperation<T>() {
                    @Override
                    public Observable<T> call(Server server) {
                        URI finalUri = reconstructURIWithServer(server, request.getUri());
                        S requestForServer = (S) request.replaceUri(finalUri);
                        try {
                            return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                        } 
                        catch (Exception e) {
                            return Observable.error(e);
                        }
                    }
                })
                .toBlocking()
                .single();
        } catch (Exception e) {
            Throwable t = e.getCause();
            if (t instanceof ClientException) {
                throw (ClientException) t;
            } else {
                throw new ClientException(e);
            }
        }

    }

上面那么多行,值需要注意其中一行就好了

 RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, requestConfig);

由于这个ribbon是结合feign使用的,所以这个获取重试机制的方法是实现在下面这个类里面:

//org.springframework.cloud.netflix.feign.ribbon.FeignLoadBalancer.java
@Override
    public RequestSpecificRetryHandler getRequestSpecificRetryHandler(
            RibbonRequest request, IClientConfig requestConfig) {
        // 1. 从ribbon的配置里面获取OkToRetryOnAllOperations的配置,默认为false,如果配置为true
        if (this.clientConfig.get(CommonClientConfigKey.OkToRetryOnAllOperations,
                false)) {
            // 
            return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
                    requestConfig);
        }
        // 2. 判断请求方式是否是POST请求
        if (!request.toRequest().method().equals("GET")) {
            return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(),
                    requestConfig);
        }
        else {
            // 3. 默认的重试handler
            return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
                    requestConfig);
        }
    }

从上面的代码可以看到,根据不同的条件,构建不一样的RequestSpecificRetryHandler,在这之前,有必要了解一下RequestSpecificRetryHandler的四个参数的

含义。

public RequestSpecificRetryHandler(boolean okToRetryOnConnectErrors, boolean okToRetryOnAllErrors, RetryHandler baseRetryHandler,  IClientConfig requestConfig);

okToRetryOnConnectErrors : 在出现connectErrors的时候,进行重试

okToRetryOnAllErrors : 只要发现实例不可用时,都进行重试

baseRetryHandler :负载均衡重试的handler,使用的是默认的DefaultLoadBalancerRetryHandler

requestConfig : 配置

了解到这些参数的作用是,咱们再来看一下,ribbon的重试

步骤说明:

1.从ribbon的配置里面获取OkToRetryOnAllOperations的配置,默认为false,如果配置为true,那么设置okToRetryOnConnectErrors = true , okToRetryOnAllErrors = true , 也就是说 配置了这个,只要发现实例不可达,那么都会进行重试

2.判断请求方式是否是POST请求,如果是POST 请求,则设置okToRetryOnConnectErrors = true , okToRetryOnAllErrors = false , 这个设置的意思,只要在

出现connectErrors的时候才会进行重试。

3.第三步,基本上表示该请求为GET请求了,这个时候是设置okToRetryOnConnectErrors = true , okToRetryOnAllErrors = true , 跟第一步的效果一样。

总结:

默认OkToRetryOnAllOperations为false, 所以上面的第一步一般是不会执行, 也就是说,POST请求会走上面的第二步,GET请求是走上面的第三步。

默认ribbon重试一次,POST请求默认是在connect异常的时候会重试,GET请求是都会进行重试,包括请求超时之类的。所以尽量不要用GET请求做增加,修改,删除的操作,

附:

connectErrors : 默认指的是这两个ConnectException , SocketTimeoutException
这里写图片描述

猜你喜欢

转载自blog.csdn.net/u012394095/article/details/82180538