SpringCloud 源码系列(11)— 负载均衡Ribbon 之 重试与总结篇

2,016 阅读10分钟

系列文章:

SpringCloud 源码系列(1)— 注册中心Eureka 之 启动初始化

SpringCloud 源码系列(2)— 注册中心Eureka 之 服务注册、续约

SpringCloud 源码系列(3)— 注册中心Eureka 之 抓取注册表

SpringCloud 源码系列(4)— 注册中心Eureka 之 服务下线、故障、自我保护机制

SpringCloud 源码系列(5)— 注册中心Eureka 之 EurekaServer集群

SpringCloud 源码系列(6)— 注册中心Eureka 之 总结篇

SpringCloud 源码系列(7)— 负载均衡Ribbon 之 RestTemplate

SpringCloud 源码系列(8)— 负载均衡Ribbon 之 核心原理

SpringCloud 源码系列(9)— 负载均衡Ribbon 之 核心组件与配置

SpringCloud 源码系列(10)— 负载均衡Ribbon 之 HTTP客户端组件

Ribbon 重试

通过前面几篇文章的分析,可以知道有重试功能的其实有两个组件,一个是 Ribbon 的 LoadBalancerCommand,一个是 spring-retry 的 RetryTemplateRetryableRibbonLoadBalancingHttpClientRetryableOkHttpLoadBalancingClient 都要依赖 RetryTemplate,所以必须先引入 spring-retry 依赖,它们最终都是使用 RetryTemplate 实现请求重试的能力的。除了 RetryTemplate,其它客户端想要获取重试的功能,就要用 ribbon 中的 AbstractLoadBalancerAwareClient 相关的组件,并调用 executeWithLoadBalancer 方法。

负载均衡客户端

1、AbstractLoadBalancerAwareClient

再看下 AbstractLoadBalancerAwareClient 的体系,通过源码可以了解到:

  • RetryableFeignLoadBalancer、RetryableRibbonLoadBalancingHttpClient、RetryableOkHttpLoadBalancingClient 都是使用 RetryTemplate 实现重试功能的,也就是 spring-retry 的重试。
  • RestClient、FeignLoadBalancer、RibbonLoadBalancingHttpClient、OkHttpLoadBalancingClient 是在 AbstractLoadBalancerAwareClient 中使用 LoadBalancerCommand 实现重试功能的,就是是 Ribbon 的重试。

2、负载均衡调用

具体的 AbstractLoadBalancerAwareClient 客户端想要负载均衡调用以及能进行重试,需调用 AbstractLoadBalancerAwareClient 的 executeWithLoadBalancer 方法。

在这个方法里面,它先构建了 LoadBalancerCommand,然后用 command 提交了一个 ServerOperation,这个 ServerOperation 中对 URI 进行了 重构,转到具体的 LoadBalancerContext 去执行请求。

public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
    // 负载均衡命令
    LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);

    try {
        return command.submit(
            new ServerOperation<T>() {
                @Override
                public Observable<T> call(Server server) {
                    // 重构URI
                    URI finalUri = reconstructURIWithServer(server, request.getUri());
                    S requestForServer = (S) request.replaceUri(finalUri);
                    try {
                        // 使用具体的 AbstractLoadBalancerAwareClient 客户端执行请求
                        return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                    }
                    catch (Exception e) {
                        return Observable.error(e);
                    }
                }
            })
            .toBlocking()
            .single();
    }
}

3、构建 LoadBalancerCommand

再看 buildLoadBalancerCommand 方法,它首先会通过 getRequestSpecificRetryHandler() 方法获取请求重试处理器 RequestSpecificRetryHandler,而 getRequestSpecificRetryHandler() 是一个抽象方法。这里就要重点注意了。

// 抽象方法,获取请求重试处理器
public abstract RequestSpecificRetryHandler getRequestSpecificRetryHandler(S request, IClientConfig requestConfig);

protected LoadBalancerCommand<T> buildLoadBalancerCommand(final S request, final IClientConfig config) {
    // 获取请求重试处理器
    RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, config);
    LoadBalancerCommand.Builder<T> builder = LoadBalancerCommand.<T>builder()
            .withLoadBalancerContext(this)
            .withRetryHandler(handler)
            .withLoadBalancerURI(request.getUri());
    customizeLoadBalancerCommandBuilder(request, config, builder);
    return builder.build();
}

请求重试处理器

RequestSpecificRetryHandler

先了解下 RequestSpecificRetryHandler:

  • 首先看它的构造方法,注意第一个参数和第二个参数,因为不同的 getRequestSpecificRetryHandler() 方法实现,主要差异就在于这两个参数。
  • 然后看 isRetriableException,这个方法就是 LoadBalancerCommand 用来判断异常后是否需要重试的方法,可以了解到 okToRetryOnAllErrors=true 时就可以重试,否则 okToRetryOnConnectErrors=true 才可能重试。需要注意的是就算这个方法返回 true 也不一定会重试,这跟重试次数也是有一定关系的。
public RequestSpecificRetryHandler(boolean okToRetryOnConnectErrors, boolean okToRetryOnAllErrors, RetryHandler baseRetryHandler, @Nullable IClientConfig requestConfig) {
    Preconditions.checkNotNull(baseRetryHandler);
    this.okToRetryOnConnectErrors = okToRetryOnConnectErrors;
    this.okToRetryOnAllErrors = okToRetryOnAllErrors;
    this.fallback = baseRetryHandler;
    if (requestConfig != null) {
        // 在同一个Server上重试的次数
        if (requestConfig.containsProperty(CommonClientConfigKey.MaxAutoRetries)) {
            retrySameServer = requestConfig.get(CommonClientConfigKey.MaxAutoRetries);
        }
        // 重试下一个Server的次数
        if (requestConfig.containsProperty(CommonClientConfigKey.MaxAutoRetriesNextServer)) {
            retryNextServer = requestConfig.get(CommonClientConfigKey.MaxAutoRetriesNextServer);
        }
    }
}

@Override
public boolean isRetriableException(Throwable e, boolean sameServer) {
    // 所有错误都重试
    if (okToRetryOnAllErrors) {
        return true;
    }
    // ClientException 才可能重试
    else if (e instanceof ClientException) {
        ClientException ce = (ClientException) e;
        if (ce.getErrorType() == ClientException.ErrorType.SERVER_THROTTLED) {
            return !sameServer;
        } else {
            return false;
        }
    }
    else  {
        // 连接错误才重试,就是抛出 SocketException 异常时才重试
        return okToRetryOnConnectErrors && isConnectionException(e);
    }
}

不同HTTP客户端的重试处理器

1、RestClient

默认配置下,RestClient 的 getRequestSpecificRetryHandler 会走到最后一步,okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 true,也就是说 isRetriableException 始终返回 true,也就是说抛出异常都会重试。非GET请求时,okToRetryOnAllErrors为 false,只有连接异常时才会重试。

@Override
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(
        HttpRequest request, IClientConfig requestConfig) {
    if (!request.isRetriable()) {
        return new RequestSpecificRetryHandler(false, false, this.getRetryHandler(), requestConfig);
    }
    if (this.ncc.get(CommonClientConfigKey.OkToRetryOnAllOperations, false)) {
        return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(), requestConfig);
    }
    if (request.getVerb() != HttpRequest.Verb.GET) {
        return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(), requestConfig);
    } else {
        // okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 true
        return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(), requestConfig);
    }
}

2、AbstractLoadBalancingClient

AbstractLoadBalancingClient 中的 getRequestSpecificRetryHandler 相当于一个默认实现,默认情况下 okToRetryOnAllOperations 为 false,最后也会到最后一步,即 okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 true,isRetriableException 始终返回 true。非GET请求时,okToRetryOnAllErrors为 false,只有连接异常时才会重试。

@Override
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(final S request, final IClientConfig requestConfig) {
    // okToRetryOnAllOperations:是否所有操作都重试,默认 false
    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);
    }
}

3、RibbonLoadBalancingHttpClient

RibbonLoadBalancingHttpClient 也重载了 getRequestSpecificRetryHandler,但是它设置了 okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 false,isRetriableException 始终返回 false。

至此我们应该就知道为什么调用 RibbonLoadBalancingHttpClient 的 executeWithLoadBalancer 不具备重试的功能的原因了。所以启用 apache httpclient 时,RibbonLoadBalancingHttpClient 调用是不支持重试的

@Override
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(RibbonApacheHttpRequest request, IClientConfig requestConfig) {
    // okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 false
    return new RequestSpecificRetryHandler(false, false, RetryHandler.DEFAULT, requestConfig);
}

RetryableRibbonLoadBalancingHttpClient 中也重写了 getRequestSpecificRetryHandler,同样也是设置 okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 false。但是在引入 spring-retry 后,它会使用 RetryTemplate 实现重试的功能。

@Override
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(RibbonApacheHttpRequest request, IClientConfig requestConfig) {
    // okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 false
    return new RequestSpecificRetryHandler(false, false, RetryHandler.DEFAULT, null);
}

4、OkHttpLoadBalancingClient

OkHttpLoadBalancingClient 并没有重写 getRequestSpecificRetryHandler,所以它是使用父类 AbstractLoadBalancingClient 中的方法,也就是 okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 true。

所以,启用 okhttp 时,OkHttpLoadBalancingClient 是支持所有GET重试的,非GET请求则在抛出连接异常(SocketException)时支持重试。

而 RetryableOkHttpLoadBalancingClient 跟 RetryableRibbonLoadBalancingHttpClient 一样的重写方式,使用 RetryTemplate 实现重试。

@Override
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(RibbonApacheHttpRequest request, IClientConfig requestConfig) {
    // okToRetryOnConnectErrors、okToRetryOnAllErrors 都为 false
    return new RequestSpecificRetryHandler(false, false, RetryHandler.DEFAULT, null);
}

LoadBalancerCommand 重试

看 LoadBalancerCommand 的 submit 方法,这个方法是重试的核心代码。

  • 首先获取了同一个Server重试次数 maxRetrysSame 和 重试下一个Server的次数 maxRetrysNext,其实就是前面配置的 ribbon.MaxAutoRetriesribbon.MaxAutoRetriesNextServer,我设置的是 1。
  • 然后创建了一个 Observable,它的第一层会先通过 loadBalancerContext 获取 Server。在重试下一个 Server 时,这里就会获取下一个 Server。
  • 在第二层,又创建了一个 Observable,这个 Observable 就是调用 ServerOperation 的,就是重构 URI,调用具体的 AbstractLoadBalancerAwareClient 执行请求。
  • 在第二层里,会根据 maxRetrysSame 重试同一个 Server,从 retryPolicy() 中可以了解到,当重试次数大于 maxRetrysSame 后,同一个 Server 重试就结束了,否则就用 retryHandler.isRetriableException() 判断是否重试,这个前面已经分析过了。
  • 在外层,则根据 maxRetrysNext 重试不同的 Server,从 retryPolicy() 中可以了解到,当不同Server重试次数大于 maxRetrysNext 后,就重试结束了,整个重试也就结束了,如果还是失败,就会进入 onErrorResumeNext 进行最后的失败处理。
public Observable<T> submit(final ServerOperation<T> operation) {
    final ExecutionInfoContext context = new ExecutionInfoContext();

    // 同一个Server重试次数
    final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer();
    // 重试下一个Server的次数
    final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer();

    // 创建一个 Observable
    Observable<T> o =
            // 使用 loadBalancerContext 获取 Server
            (server == null ? selectServer() : Observable.just(server))
            .concatMap(new Func1<Server, Observable<T>>() {
                @Override
                public Observable<T> call(Server server) {
                    // 设置Server
                    context.setServer(server);
                    final ServerStats stats = loadBalancerContext.getServerStats(server);

                    // 创建 Observable
                    Observable<T> o = Observable
                            .just(server)
                            .concatMap(new Func1<Server, Observable<T>>() {
                                @Override
                                public Observable<T> call(final Server server) {
                                    // 增加尝试次数
                                    context.incAttemptCount();
                                    // ...
                                    // 调用 ServerOperation
                                    return operation.call(server).doOnEach(new Observer<T>() {
                                        // 一些回调方法
                                    });
                                }
                            });
                    // 重试同一个Server
                    if (maxRetrysSame > 0)
                        o = o.retry(retryPolicy(maxRetrysSame, true));
                    return o;
                }
            });

    if (maxRetrysNext > 0 && server == null)
        // 重试不同Server
        o = o.retry(retryPolicy(maxRetrysNext, false));

    return o.onErrorResumeNext(new Func1<Throwable, Observable<T>>() {
        @Override
        public Observable<T> call(Throwable e) {
            // 异常处理
            return Observable.error(e);
        }
    });
}

// retryPolicy 返回一个是否重试的断言
private Func2<Integer, Throwable, Boolean> retryPolicy(final int maxRetrys, final boolean same) {
    return new Func2<Integer, Throwable, Boolean>() {
        @Override
        public Boolean call(Integer tryCount, Throwable e) {
            // 请求拒绝异常就不允许重试
            if (e instanceof AbortExecutionException) {
                return false;
            }
            // 尝试次数是否大于最大重试次数
            if (tryCount > maxRetrys) {
                return false;
            }
            // 使用 RequestSpecificRetryHandler 判断是否重试
            return retryHandler.isRetriableException(e, same);
        }
    };
}

最后来总结一下 LoadBalancerCommand 重试:

  • 重试分为同一个 Server 重试和重试下一个Server,当重试次数大于设置的重试值时,就停止重试。否则通过 retryHandler.isRetriableException() 判断是否重试。
  • 那这里一共请求了多少次呢?可以总结出如下公式:请求次数 = (maxRetrysSame + 1) * (maxRetrysNext + 1),所以按 ribbon.MaxAutoRetries = 1、ribbon.MaxAutoRetriesNextServer = 1 的配置,如果每次请求都超时,总共就会发起 4 次请求。

RetryTemplate 重试

spring-retry

要启用 RetryTemplate 需先引入 spring-retry

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>

RetryableRibbonLoadBalancingHttpClient 为例,先看看它的 execute 方法,它先创建了负载均衡重试策略类 LoadBalancedRetryPolicy,然后将请求调用的逻辑封装到 RetryCallback 中,最后其实就是用 RetryTemplate 执行这个 RetryCallback,也就是说请求重试的逻辑都在 RetryTemplate 中。

public RibbonApacheHttpResponse execute(final RibbonApacheHttpRequest request, final IClientConfig configOverride) throws Exception {
    //...
    // 负载均衡重试策略 RibbonLoadBalancedRetryPolicy
    final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryFactory.createRetryPolicy(this.getClientName(), this);

    RetryCallback<RibbonApacheHttpResponse, Exception> retryCallback = context -> {
        // ...
        // delegate => CloseableHttpClient
        final HttpResponse httpResponse = RetryableRibbonLoadBalancingHttpClient.this.delegate.execute(httpUriRequest);
        // ...
        // 成功 返回结果
        return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI());
    };

    return this.executeWithRetry(request, retryPolicy, retryCallback, recoveryCallback);
}

private RibbonApacheHttpResponse executeWithRetry(RibbonApacheHttpRequest request, LoadBalancedRetryPolicy retryPolicy,
        RetryCallback<RibbonApacheHttpResponse, Exception> callback,
        RecoveryCallback<RibbonApacheHttpResponse> recoveryCallback) throws Exception {
    RetryTemplate retryTemplate = new RetryTemplate();

    // retryable => 取自 RibbonCommandContext 设置的 retryable 参数
    boolean retryable = isRequestRetryable(request);
    // 设置重试策略
    retryTemplate.setRetryPolicy(retryPolicy == null || !retryable
            ? new NeverRetryPolicy() : new RetryPolicy(request, retryPolicy, this, this.getClientName()));

    BackOffPolicy backOffPolicy = loadBalancedRetryFactory.createBackOffPolicy(this.getClientName());
    retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);

    // 利用 retryTemplate 执行请求 callback
    return retryTemplate.execute(callback, recoveryCallback);
}

需要注意的是,在 executeWithRetry 中,会判断是否要重试,判断的逻辑中 getRetryable 其实就是取的 ApacheClientHttpRequest 中 executeInternal 方法里创建的 RibbonCommandContext 设置的 retryable 参数,这就和前面定制化的逻辑衔接上了。

private boolean isRequestRetryable(ContextAwareRequest request) {
    if (request.getContext() == null || request.getContext().getRetryable() == null) {
        return true;
    }
    return request.getContext().getRetryable();
}

RetryTemplate

进入 RetryTemplate 的 execute 方法,核心的逻辑我精简成如下代码,主要就是一个 while 循环判断是否可以重试,然后调用 retryCallback 执行请求。请求失败后,比如超时,抛出异常,就会用 registerThrowable() 来注册异常。

protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback,
        RecoveryCallback<T> recoveryCallback, RetryState state) throws E, ExhaustedRetryException {

    // retryPolicy => InterceptorRetryPolicy
    RetryPolicy retryPolicy = this.retryPolicy;
    BackOffPolicy backOffPolicy = this.backOffPolicy;
    //....
    try {
        // ...
        // canRetry 判断是否重试
        while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
            try {
                // retryCallback 调用
                return retryCallback.doWithRetry(context);
            }
            catch (Throwable e) {
                // ...
                // 注册异常
                registerThrowable(retryPolicy, state, context, e);
                // ...
            }
        }
        exhausted = true;
        return handleRetryExhausted(recoveryCallback, context, state);
    }
    //...
}

canRetry 方法,它实际是调用了 InterceptorRetryPolicycanRetry。第一次调用时,会去获取 Server;否则就用 RibbonLoadBalancedRetryPolicy 判断是否重试下一个 Server,注意它判断的逻辑是 GET 请求或者允许所有操作操作重试,且 Server 重试次数 nextServerCount 小于等于配置的 MaxAutoRetriesNextServer。也就是说,while 循环判断的 canRetry 是重试下一个 Server 的

protected boolean canRetry(RetryPolicy retryPolicy, RetryContext context) {
    return retryPolicy.canRetry(context);
}

//////////// InterceptorRetryPolicy
public boolean canRetry(RetryContext context) {
    LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
    if (lbContext.getRetryCount() == 0 && lbContext.getServiceInstance() == null) {
        // 获取 Server
        lbContext.setServiceInstance(this.serviceInstanceChooser.choose(this.serviceName));
        return true;
    }
    // RibbonLoadBalancedRetryPolicy => 重试下一个Server
    return this.policy.canRetryNextServer(lbContext);
}

///////// RibbonLoadBalancedRetryPolicy
public boolean canRetryNextServer(LoadBalancedRetryContext context) {
    // 判断重试下一个Server
    return nextServerCount <= lbContext.getRetryHandler().getMaxRetriesOnNextServer()
            && canRetry(context);
}

public boolean canRetry(LoadBalancedRetryContext context) {
    // GET 请求或者允许所有操作重试时,就允许重试
    HttpMethod method = context.getRequest().getMethod();
    return HttpMethod.GET == method || lbContext.isOkToRetryOnAllOperations();
}

接着看请求失败后的注册异常 registerThrowable(),它最后会向 RibbonLoadBalancedRetryPolicy 注册异常。在 RibbonLoadBalancedRetryPolicy 的 registerThrowable() 方法中,如果不重试同一个Server且可以重试下一个Server,就会轮询获取下一个Server。如果可以在同一个Server上重试,sameServerCount 计数器就+1,否则重置 sameServerCount,然后 nextServerCount +1

protected void registerThrowable(RetryPolicy retryPolicy, RetryState state,
        RetryContext context, Throwable e) {
    retryPolicy.registerThrowable(context, e);
    registerContext(context, state);
}

///////// InterceptorRetryPolicy /////////
public void registerThrowable(RetryContext context, Throwable throwable) {
    LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
    lbContext.registerThrowable(throwable);
    // RibbonLoadBalancedRetryPolicy
    this.policy.registerThrowable(lbContext, throwable);
}

///////// RibbonLoadBalancedRetryPolicy /////////
public void registerThrowable(LoadBalancedRetryContext context, Throwable throwable) {
    //...
    // 如果不在在同一个Server 上重试且可以重试下一个Server,则重新选择一个 Server
    if (!canRetrySameServer(context) && canRetryNextServer(context)) {
        context.setServiceInstance(loadBalanceChooser.choose(serviceId));
    }

    // 同一个Server重试超过设置的值后,就重置 sameServerCount
    if (sameServerCount >= lbContext.getRetryHandler().getMaxRetriesOnSameServer()
            && canRetry(context)) {
        // 重置 nextServerCount
        sameServerCount = 0;
        // 下一个Server重试次数+1
        nextServerCount++;
        if (!canRetryNextServer(context)) {
            // 不能重试下一个Server了
            context.setExhaustedOnly();
        }
    }
    else {
        // 同一个Server重试次数+1
        sameServerCount++;
    }
}

// 判断是否重试同一个Server
public boolean canRetrySameServer(LoadBalancedRetryContext context) {
    return sameServerCount < lbContext.getRetryHandler().getMaxRetriesOnSameServer()
            && canRetry(context);
}

public boolean canRetry(LoadBalancedRetryContext context) {
    // GET 请求或者允许所有操作重试时,就允许重试
    HttpMethod method = context.getRequest().getMethod();
    return HttpMethod.GET == method || lbContext.isOkToRetryOnAllOperations();
}

Ribbon 与 RetryTemplate 重试总结

1、首先,Ribbon 关于超时和重试的配置参数如下,这些参数也可以针对某个客户端配置

ribbon:
  # 客户端读取超时时间
  ReadTimeout: 1000
  # 客户端连接超时时间
  ConnectTimeout: 1000
  # 默认只重试 GET,设置为 true 时将重试所有类型,如 POST、PUT、DELETE
  OkToRetryOnAllOperations: false
  # 同一个Server重试次数
  MaxAutoRetries: 1
  # 最多重试几个Server
  MaxAutoRetriesNextServer: 1

2、RetryTemplate 是 spring-retry 的重试组件,LoadBalancerCommand 是 Ribbon 的重试组件。它们重试的请求次数是一样的,重试逻辑也是类似,都是先重试当前 Server,再重试下一个Server,总的请求次数 = (MaxAutoRetries + 1) * (MaxAutoRetriesNextServer + 1)

3、但是有点差别的是,RetryTemplate 会判断请求方法为 GET 或者 OkToRetryOnAllOperations=true 时才允许重试,而 LoadBalancerCommand 是GET方法都可以重试,非GET方法在抛出连接异常时也可以重试。这个要注意下,一般只有GET才允许重试,因为GET是查询操作,接口是幂等的,而POST、PUT、DELETE一般是非幂等的。所以一般更建议使用 RetryTemplate,并且配置 OkToRetryOnAllOperations=false

4、为了提升服务间通信性能,一般可以启用 apache HttpClient 或者 OkHttp,如果要启用重试功能,还需要引入 spring-retry 依赖。重试时,当前Server就不要重试了(MaxAutoRetries=0),直接重试下一个Server。

ribbon:
  # 客户端读取超时时间
  ReadTimeout: 1000
  # 客户端连接超时时间
  ConnectTimeout: 1000
  # 默认只重试 GET,设置为 true 时将重试所有类型,如 POST、PUT、DELETE
  OkToRetryOnAllOperations: false
  # 同一个Server重试次数
  MaxAutoRetries: 0
  # 最多重试几个Server
  MaxAutoRetriesNextServer: 1
  # 启用 httpclient
  httpclient:
    enabled: false
  # 启用 RestClient
  restclient:
    enabled: false
  # 启用 okhttp
  okhttp:
    enabled: true

Ribbon 架构总结

最后,将 Ribbon 核心组件架构用两张类图总结下。

负载均衡器 ILoadBalancer

负载均衡客户端