Spring Cloud Ribbon 全解 (7) - SpringCloud环境下纯Ribbon(不包含Eureka)重试配置 干货满满张哈希

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
网络型负载均衡 NLB,每月750个小时 15LCU
EMR Serverless StarRocks,5000CU*H 48000GB*H
简介: Spring Cloud Ribbon 全解 (7) - SpringCloud环境下纯Ribbon(不包含Eureka)重试配置干货满满张哈希

本文基于SpringCloud-Dalston.SR5

前面已经分析了RibbonSpringCloud环境下纯Ribbon(不包含Eureka)使用与启动,接下来我们来看看重试的配置:


SpringCloud环境下纯Ribbon(不包含Eureka)重试配置


示例项目

以下项目可以参考:https://github.com/HashZhang/ScanfoldAll/tree/master/Scanfold-SpringCloud/Scanfold-SpringCloud-Ribbon/Scanfold-SpringCloud-RibbonOnly

增加重试的配置很简单,只要将Spring-retry的依赖添加至classpath即可。

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

需要注意,Spring-retry这个依赖被添加之后,如果你的项目还在使用Feign和Zuul,那么Feign和Zuul也会同时拥有重试机制。这个我们在之后的章节会讨论。

加入这个依赖,并在application.properties增加配置:

#最多重试多少台服务器
default-test.ribbon.MaxAutoRetriesNextServer=1
#每台服务器最多重试次数,但是首次调用不包括在内
default-test.ribbon.MaxAutoRetries=1
#哪些状态码会重试
default-test.ribbon.retryableStatusCodes=500
#是否所有方法都重试,如果为false那么只重试get方法
default-test.ribbon.OkToRetryOnAllOperations=true

再启动应用,我们会发现,触发了重试。

虽然之前在上一节里面,我们看到RibbonClientConfiguration虽然初始化了一个DefaultRetryHandler这样的Bean,但是其实,没啥用,必须引入Spring-retry这个依赖,上面的重试配置才会生效。


源码分析


在加入spring-retry这个依赖之后,之前提到的LoadBalanceInterceptor被替换成了RetryLoadBalancerInterceptor,在这里intercept的代码如下:


RetryLoadBalancerInterceptor.java

public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
                    final ClientHttpRequestExecution execution) throws IOException {
  final URI originalUri = request.getURI();
  final String serviceName = originalUri.getHost();
  Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
  //利用微服务名称还有LoadBalancerClient,创建一个LoadBalancedRetryPolicy
  final LoadBalancedRetryPolicy retryPolicy = lbRetryPolicyFactory.create(serviceName,
      loadBalancer);
  //创建一个RetryTemplate
  RetryTemplate template = this.retryTemplate == null ? new RetryTemplate() : this.retryTemplate;
  //设置如果重试次数用尽,是否将最后一个Exception抛出
  template.setThrowLastExceptionOnExhausted(true);
  //设置retryPolicy
  template.setRetryPolicy(
      !lbProperties.isEnabled() || retryPolicy == null ? new NeverRetryPolicy()
          : new InterceptorRetryPolicy(request, retryPolicy, loadBalancer,
          serviceName));
  //设置执行回调并执行请求
  return template
      .execute(new RetryCallback<ClientHttpResponse, IOException>() {
        @Override
        public ClientHttpResponse doWithRetry(RetryContext context)
            throws IOException {
          ServiceInstance serviceInstance = null;
          //之后我们会看到,在调用异常,发生重试并切换server时,就是从LoadBalancer中重新choose一个Server并放入这个lbContext,在这里把他取出来
          if (context instanceof LoadBalancedRetryContext) {
            LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
            serviceInstance = lbContext.getServiceInstance();
          }
          //如果是null代表是第一次被回调
          if (serviceInstance == null) {
            serviceInstance = loadBalancer.choose(serviceName);
          }
          //在这个serviceInstance上面执行Request
          ClientHttpResponse response = RetryLoadBalancerInterceptor.this.loadBalancer.execute(
              serviceName, serviceInstance,
              requestFactory.createRequest(request, body, execution));
          //获取返回statusCode
          int statusCode = response.getRawStatusCode();
          //检查是否需要并可以重试
          if(retryPolicy != null && retryPolicy.retryableStatusCode(statusCode)) {
            response.close();
            throw new RetryableStatusCodeException(serviceName, statusCode);
          }
          return response;
        }
      });
}

创建的LoadBalancedRetryPolicy是重试的Policy,就是RibbonLoadBalancedRetryPolicy:

RibbonLoadBalancedRetryPolicy.java

public static final IClientConfigKey<String> RETRYABLE_STATUS_CODES = new CommonClientConfigKey<String>("retryableStatusCodes") {};
private int sameServerCount = 0;
private int nextServerCount = 0;
private String serviceId;
private RibbonLoadBalancerContext lbContext;
private ServiceInstanceChooser loadBalanceChooser;
List<Integer> retryableStatusCodes = new ArrayList<>();
public RibbonLoadBalancedRetryPolicy(String serviceId, RibbonLoadBalancerContext context, ServiceInstanceChooser loadBalanceChooser,
                                     IClientConfig clientConfig) {
    this.serviceId = serviceId;
    this.lbContext = context;
    this.loadBalanceChooser = loadBalanceChooser;
    String retryableStatusCodesProp = clientConfig.getPropertyAsString(RETRYABLE_STATUS_CODES, "");
    String[] retryableStatusCodesArray = retryableStatusCodesProp.split(",");
    for(String code : retryableStatusCodesArray) {
        if(!StringUtils.isEmpty(code)) {
            try {
                retryableStatusCodes.add(Integer.valueOf(code.trim()));
            } catch (NumberFormatException e) {
                //TODO log
            }
        }
    }
}

在这个项目里,我们设置了retryableStatusCodes是500.

我们再来看RetryTemplate的执行Request的方法:

protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback,
    RecoveryCallback<T> recoveryCallback, RetryState state)
    throws E, ExhaustedRetryException {
  //初始化代码略
    //当还可以retry,并且没有耗尽重试次数
    while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
      try {
        //初始化,清除掉lastException,因为最后超过重试次数还没成功的话,要抛出最后的lastException
        lastException = null;
        //回调上面的执行回调,如果调用成功就会返回这个响应
        //上面回调会检查响应码,在我们上面项目配置中,如果响应码为500,就会抛出RetryableStatusCodeException,其他响应吗因为没有抛出异常,所以不会触发重试
        return retryCallback.doWithRetry(context);
      }
      catch (Throwable e) {
                //捕获异常,记录到lastException
        lastException = e;
        try {
            //主要就是调用Policy的registerThrowable
          registerThrowable(retryPolicy, state, context, e);
        }
        catch (Exception ex) {
          throw new TerminatedRetryException("Could not register throwable",
              ex);
        }
        finally {
          doOnErrorInterceptors(retryCallback, context, e);
        }
    }
  //后续处理代码略...
}

我们先来看看canRetry的判断是怎么判断的: 其实就是RibbonLoadBalancedRetryPolicy的canRetry方法:


RibbonLoadBalancedRetryPolicy.java

public boolean canRetry(LoadBalancedRetryContext context) {
    HttpMethod method = context.getRequest().getMethod();
    //如果是Get方法,就能Retry
    //或者配置了isOkToRetryOnAllOperations为true(默认是false),就无论什么httpmethod都重试
    return HttpMethod.GET == method || lbContext.isOkToRetryOnAllOperations();
}

抛出RetryableStatusCodeException需要判断的retryPolicy.retryableStatusCode(statusCode)RibbonLoadBalancedRetryPolicy.java

public boolean retryableStatusCode(int statusCode) {
    //retryableStatusCodes就是default-test.ribbon.retryableStatusCodes=500这个配置
    return retryableStatusCodes.contains(statusCode);
}

在调用发生异常的时候,会调用这个Policy的registerThrowable方法:

public void registerThrowable(LoadBalancedRetryContext context, Throwable throwable) {
    //检查
    if(!canRetrySameServer(context)  && canRetryNextServer(context)) {
        context.setServiceInstance(loadBalanceChooser.choose(serviceId));
    }
    //当sameServerCount大于等于MaxRetriesOnSameServer(就是default-test.ribbon.MaxAutoRetries这个配置)并且可以retry的时候
    if(sameServerCount >= lbContext.getRetryHandler().getMaxRetriesOnSameServer() && canRetry(context)) {
        //由于切换到下一个server,所以就置零sameServerCount
        sameServerCount = 0;
        nextServerCount++;
        //如果不能canRetryNextServer,就表明耗尽重试次数
        if(!canRetryNextServer(context)) {
            context.setExhaustedOnly();
        }
    } else {
        sameServerCount++;
    }
}

总结起来,流程如下图所示:


微信图片_20220624113614.jpg

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
5月前
|
XML Java UED
使用 Spring Boot 实现重试和补偿功能:从理论到实践
【6月更文挑战第17天】在分布式系统中,服务之间的调用可能会因为网络故障、服务器负载等原因偶尔失败。为了提高系统的可靠性和稳定性,我们经常需要实现重试和补偿功能。
111 6
|
2月前
|
负载均衡 Java Nacos
SpringCloud基础1——远程调用、Eureka,Nacos注册中心、Ribbon负载均衡
微服务介绍、SpringCloud、服务拆分和远程调用、Eureka注册中心、Ribbon负载均衡、Nacos注册中心
SpringCloud基础1——远程调用、Eureka,Nacos注册中心、Ribbon负载均衡
|
3月前
|
负载均衡 算法 Java
SpringCloud之Ribbon使用
通过 Ribbon,可以非常便捷的在微服务架构中实现请求负载均衡,提升系统的高可用性和伸缩性。在实际使用中,需要根据实际场景选择合适的负载均衡策略,并对其进行适当配置,以达到更佳的负载均衡效果。
55 13
|
5月前
|
负载均衡 算法 Java
Spring Cloud Netflix 之 Ribbon
Spring Cloud Netflix Ribbon是客户端负载均衡器,用于在微服务架构中分发请求。它与RestTemplate结合,自动在服务发现(如Eureka)注册的服务之间进行调用。配置包括在pom.xml中添加依赖,设置application.yml以连接Eureka服务器,并在配置类中创建@LoadBalanced的RestTemplate。通过这种方式,当调用如`/user/userInfoList`的接口时,Ribbon会自动处理到多个可用服务实例的负载均衡。
|
5月前
|
缓存 负载均衡 Java
Java一分钟之-Spring Cloud Netflix Ribbon:客户端负载均衡
【6月更文挑战第9天】Spring Cloud Netflix Ribbon是客户端负载均衡器,用于服务间的智能路由。本文介绍了Ribbon的基本概念、快速入门步骤,包括添加依赖、配置服务调用和使用RestTemplate。此外,还讨论了常见问题,如服务实例选择不均、超时和重试设置不当、服务列表更新不及时,并提供了相应的解决策略。最后,展示了如何自定义负载均衡策略。理解并正确使用Ribbon能提升微服务架构的稳定性和效率。
186 3
|
5月前
|
缓存 Java Maven
深入解析Google Guava库与Spring Retry重试框架
深入解析Google Guava库与Spring Retry重试框架
|
6月前
|
负载均衡
【SpringCloud】Ribbon负载均衡原理、负载均衡策略、饥饿加载
【SpringCloud】Ribbon负载均衡原理、负载均衡策略、饥饿加载
78 0
|
Dubbo Java 应用服务中间件
深入了解Spring Cloud Alibaba Dubbo
在现代分布式系统开发中,构建高性能、可伸缩性和弹性的微服务架构变得越来越重要。Spring Cloud Alibaba Dubbo(简称Dubbo)是一个开源的分布式服务框架,可以帮助开发者构建强大的微服务架构,具备负载均衡、服务治理、远程调用等强大功能。本文将深入介绍Spring Cloud Alibaba Dubbo,帮助你理解它的核心概念、工作原理以及如何在你的项目中使用它。
|
12月前
|
Kubernetes Java 微服务
Spring Boot 单体应用一键升级成 Spring Cloud Alibaba(1)
Spring Boot 单体应用一键升级成 Spring Cloud Alibaba(1)
131 0
Spring Boot 单体应用一键升级成 Spring Cloud Alibaba(1)
|
6月前
|
Java 中间件 开发者
Spring Cloud Alibaba
【1月更文挑战第27天】【1月更文挑战第127篇】Spring Cloud Alibaba
133 1