Spring Could Ribbon实战

本文介绍Spring Cloud中Ribbon组件的应用,包括负载均衡的实现方式与重试机制的配置方法。通过实例展示了如何搭建Eureka Server、Eureka Client-Provider与Eureka Client-Consumer,以及如何实现服务间的负载均衡和重试策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Ribbon

简介

Ribbon是Netflix的一个开源组件,Spring进行了封装,可用实现应用级别的负载均衡、请求重发等功能。

Ribbon负载均衡

应用场景:在高并发请求场景下,为了减轻单个服务节点的压力,将流量分流到不同应用节点,就需要用到负载均衡。

原理:ribbon组件结合RestTemplate来实现负载均衡,只需给restTemplate加上@LoadBalanced注解就可以实现。ribbon是根据服务名称application.name来做负载均衡的。

搭建EurekaServer
  1. 配置pom.xml

    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
      </dependency>
    </dependencies>
    
    <dependencyManagement>
      <dependencies>
        <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-dependencies</artifactId>
          <!--   2.3.8版本boot支持cloud Hoxton.SR11 版本,更高的boot需要cloud Hoxton 以上才可 ,详情参考spring.io -->
          <version>Hoxton.SR11</version>
          <type>pom</type>
          <scope>import</scope>
        </dependency>
      </dependencies>
    </dependencyManagement>
    
    <build>
      <!--  jar或war包名称  -->
      <finalName>springcloud-ribbon-eureka-server</finalName>
      <plugins>
        <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
      </plugins>
    </build>
    
  2. 配置application.properties

    # 应用名称
    spring.application.name=eureka-server
    # 服务端口
    server.port=8001
    # eureka的主机名称
    eureka.instance.hostname=eureka1
    # 客户端配置-注册中心访问地址, eureka1是在Hosts配置的映射,替代域名的作用。
    eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka
    
  3. 添加启动类

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
    
    @SpringBootApplication
    @EnableEurekaServer
    public class SpringcloudRibbonEurekaServerApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringcloudRibbonEurekaServerApplication.class, args);
        }
    
    }
    
搭建EurekaClient-Provider
  1. 配置pom

    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
    </dependencies>
    
    <dependencyManagement>
      <dependencies>
        <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-dependencies</artifactId>
          <!--   2.3.8版本boot支持cloud Hoxton.SR11 版本,更高的boot需要cloud Hoxton 以上才可 ,详情参考spring.io -->
          <version>Hoxton.SR11</version>
          <type>pom</type>
          <scope>import</scope>
        </dependency>
      </dependencies>
    </dependencyManagement>
    
    <build>
      <finalName>springcloud-ribbon-eureka-provider</finalName>
      <plugins>
        <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
      </plugins>
    </build>
    
  2. 配置application

    server.port=7001
    spring.application.name=provider-service
    
    # 注册到eureka显示IP
    eureka.instance.prefer-ip-address=true
    # 租期到期间隔,eureka服务器从收到最后一次心跳开始等待的时间(以秒为单位); 该值必须至少高于“leaseRenewalIntervalInSeconds”中指定的值
    eureka.instance.lease-expiration-duration-in-seconds=10
    # 租期更新间隔,eureka客户端需要发送心跳到eureka服务器的频率(以秒为单位)
    eureka.instance.lease-renewal-interval-in-seconds=5
    # 开启健康监测, 必须引入spring-boot-starter-actuator的jar包
    eureka.client.healthcheck.enabled=true
    # 注册中心地址
    eureka.client.service-url.defaultZone=http://eureka1:8001/eureka
    
  3. 添加启动类

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    
    @SpringBootApplication
    @EnableDiscoveryClient
    public class SpringcloudRibbonEurekaProviderApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringcloudRibbonEurekaProviderApplication.class, args);
        }
    
    }
    
    
  4. 添加接口服务

    import com.alibaba.fastjson.JSON;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.TimeUnit;
    
    @Slf4j
    @RestController
    public class UserController {
        private static List<User> users = new ArrayList<>();
    
        @GetMapping("getUser")
        public User getUser(@RequestParam String id) throws InterruptedException {
            log.info("provider-service ------getUser 获取用户信息--------id: {}",id);
            TimeUnit.SECONDS.sleep(2);
            return new User("001","张三",18);
        }
    
        @PostMapping("postUser")
        public List<User> postUser(@RequestBody User requestUser) throws InterruptedException {
            log.info("provider-service ------postUser 获取用户信息--------requestUser: {}", JSON.toJSONString(requestUser));
            TimeUnit.SECONDS.sleep(2);
            users.add(requestUser);
            return users;
        }
    }
    
搭建EurekaClient-Provider-Second

同理,按照搭建EurekaClient-Provider的方式再搭一套服务提供者,这两个服务用同样的服务名称。通过端口号来区分开即可。

  1. 配置pom

    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
    </dependencies>
    
    <dependencyManagement>
      <dependencies>
        <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-dependencies</artifactId>
          <!--   2.3.8版本boot支持cloud Hoxton.SR11 版本,更高的boot需要cloud Hoxton 以上才可 ,详情参考spring.io -->
          <version>Hoxton.SR11</version>
          <type>pom</type>
          <scope>import</scope>
        </dependency>
      </dependencies>
    </dependencyManagement>
    
    <build>
      <finalName>springcloud-ribbon-eureka-provider-second</finalName>
      <plugins>
        <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
      </plugins>
    </build>
    
  2. 配置application

    server.port=7002
    spring.application.name=provider-service
    
    # 注册到eureka显示IP
    eureka.instance.prefer-ip-address=true
    # 租期到期间隔,eureka服务器从收到最后一次心跳开始等待的时间(以秒为单位); 该值必须至少高于“leaseRenewalIntervalInSeconds”中指定的值
    eureka.instance.lease-expiration-duration-in-seconds=10
    # 租期更新间隔,eureka客户端需要发送心跳到eureka服务器的频率(以秒为单位)
    eureka.instance.lease-renewal-interval-in-seconds=5
    # 开启健康监测, 必须引入spring-boot-starter-actuator的jar包
    eureka.client.healthcheck.enabled=true
    # 注册中心地址
    eureka.client.service-url.defaultZone=http://eureka1:8001/eureka
    
  3. 添加启动类

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    
    @SpringBootApplication
    @EnableDiscoveryClient
    public class SpringcloudRibbonEurekaProviderSecondApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringcloudRibbonEurekaProviderSecondApplication.class, args);
        }
    
    }
    
    
  4. 添加接口服务

    import com.alibaba.fastjson.JSON;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.TimeUnit;
    
    @Slf4j
    @RestController
    public class UserController {
        private static List<User> users = new ArrayList<>();
    
        @GetMapping("getUser")
        public User getUser(@RequestParam String id) throws InterruptedException {
            log.info("provider-second-service ------getUser 获取用户信息--------id: {}",id);
            TimeUnit.SECONDS.sleep(2);
            return new User("001","张三",18);
        }
    
        @PostMapping("postUser")
        public List<User> postUser(@RequestBody User requestUser) throws InterruptedException {
            log.info("provider-second-service ------postUser 获取用户信息--------requestUser: {}", JSON.toJSONString(requestUser));
            TimeUnit.SECONDS.sleep(2);
            users.add(requestUser);
            return users;
        }
    }
    
搭建EurekaClient-Consumer
  1. 配置pom

    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
      </dependency>
    </dependencies>
    
    <dependencyManagement>
      <dependencies>
        <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-dependencies</artifactId>
          <!--   2.3.8版本boot支持cloud Hoxton.SR11 版本,更高的boot需要cloud Hoxton 以上才可 ,详情参考spring.io -->
          <version>Hoxton.SR11</version>
          <type>pom</type>
          <scope>import</scope>
        </dependency>
      </dependencies>
    </dependencyManagement>
    
    <build>
      <finalName>springcloud-ribbon-eureka-consumer</finalName>
      <plugins>
        <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
      </plugins>
    </build>
    
  2. 配置application

    server.port=7003
    spring.application.name=consumer-service
    
    # 注册到eureka显示IP
    eureka.instance.prefer-ip-address=true
    # 租期到期间隔,eureka服务器从收到最后一次心跳开始等待的时间(以秒为单位); 该值必须至少高于“leaseRenewalIntervalInSeconds”中指定的值
    eureka.instance.lease-expiration-duration-in-seconds=10
    # 租期更新间隔,eureka客户端需要发送心跳到eureka服务器的频率(以秒为单位)
    eureka.instance.lease-renewal-interval-in-seconds=5
    # 开启健康监测, 必须引入spring-boot-starter-actuator的jar包
    eureka.client.healthcheck.enabled=true
    # 注册中心地址
    eureka.client.service-url.defaultZone=http://eureka1:8001/eureka
    
  3. 添加启动类

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.context.annotation.Bean;
    import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
    import org.springframework.web.client.RestTemplate;
    
    @SpringBootApplication
    @EnableDiscoveryClient
    public class SpringcloudRibbonEurekaConsumerApplication {
    
        @Bean // 交给spring管理
        @LoadBalanced // 开启负载均衡
        public RestTemplate restTemplate(){
            HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory 
              = new HttpComponentsClientHttpRequestFactory();
            httpComponentsClientHttpRequestFactory.setConnectTimeout(10000);
            httpComponentsClientHttpRequestFactory.setConnectionRequestTimeout(10000);
            httpComponentsClientHttpRequestFactory.setReadTimeout(20000);
            return new RestTemplate(httpComponentsClientHttpRequestFactory);
        }
    
        public static void main(String[] args) {
            SpringApplication.run(SpringcloudRibbonEurekaConsumerApplication.class, args);
        }
    
    }
    
启动测试

启动所有服务,8001端口服务是Eureka注册中心,7001和7002是模拟同一个服务提供者,7003是服务消费者。

我们来访问注册中心验证一下

访问consumer接口来观察provider控制台打印信息,测试负载均衡:

访问三次http://localhost:7003/get接口

consumer控制台如下:

2021-06-18 16:02:56.914  INFO 66739 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints via configuration
2021-06-18 16:07:56.920  INFO 66739 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints via configuration
2021-06-18 16:12:56.930  INFO 66739 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints via configuration
2021-06-18 16:15:12.584  INFO 66739 --- [nio-7003-exec-1] c.e.s.c.r.e.c.api.ConsumerController     : 成功获取到数据:{"id":"001","name":"张三","age":18}
2021-06-18 16:15:16.285  INFO 66739 --- [nio-7003-exec-2] c.e.s.c.r.e.c.api.ConsumerController     : 成功获取到数据:{"id":"001","name":"张三","age":18}
2021-06-18 16:15:19.572  INFO 66739 --- [nio-7003-exec-3] c.e.s.c.r.e.c.api.ConsumerController     : 成功获取到数据:{"id":"001","name":"张三","age":18}

provider控制台如下:

2021-06-18 16:06:02.594  INFO 68778 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints via configuration
2021-06-18 16:11:02.600  INFO 68778 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints via configuration
2021-06-18 16:15:14.259  INFO 68778 --- [nio-7001-exec-1] c.e.s.c.r.e.provider.api.UserController  : provider-service ------getUser 获取用户信息--------id: 001
2021-06-18 16:16:02.612  INFO 68778 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints via configuration

Provider-second控制台如下:

2021-06-18 16:02:06.544  INFO 66614 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints via configuration
2021-06-18 16:07:06.553  INFO 66614 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints via configuration
2021-06-18 16:12:06.562  INFO 66614 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints via configuration
2021-06-18 16:15:10.577  INFO 66614 --- [nio-7002-exec-7] c.e.s.c.r.e.p.second.api.UserController  : provider-second-service ------getUser 获取用户信息--------id: 001
2021-06-18 16:15:17.565  INFO 66614 --- [nio-7002-exec-8] c.e.s.c.r.e.p.second.api.UserController  : provider-second-service ------getUser 获取用户信息--------id: 001

由上可知consumer访问3次接口,分别调用到了provider1次,provider-second2次,实现了负载均衡的效果。

Ribbon Retry重试机制

应用场景:通常在服务调用时可能服务提供者无法正常提供服务,正在重启或服务已经宕机,那么可以利用重试机制来重新发送请求,一直到服务可用,一定程度上保证业务可用。

Spring原生Retry
  1. 配置pom.xml

    <!-- spring 原生retry组件 -->
    <dependency>
      <groupId>org.springframework.retry</groupId>
      <artifactId>spring-retry</artifactId>
    </dependency>
    
  2. 编写重试接口

    import lombok.extern.slf4j.Slf4j;
    import org.springframework.remoting.RemoteAccessException;
    import org.springframework.retry.annotation.Backoff;
    import org.springframework.retry.annotation.Recover;
    import org.springframework.retry.annotation.Retryable;
    import org.springframework.stereotype.Service;
    
    /**
     * spring原生retry组件测试,
     * 可以通过
     * @see com.example.springcloud.ribbon.retry.SpringcloudRibbonRetryApplicationTests#retryTest() 测试类进行演示。
     */
    @Slf4j
    @Service
    public class UserService {
    
        @Retryable(
                value = {RemoteAccessException.class},// 发生这个异常进行重试
                maxAttempts = 5,// 重试次数,默认3次
                backoff = @Backoff(delay = 2000L,multiplier = 1) // 延迟3秒进行重试,单线程处理
        )
        public void run(){
            log.info("--------run-------------");
            throw new NullPointerException("执行逻辑空指针了...");
    //        throw new RemoteAccessException("远程调用失败...");
        }
    
        /**
         * 最后一次失败后调用此方法
         * @param e 异常
         */
        @Recover
        public void recover(RemoteAccessException e){
            log.warn("RAE最终处理的结果是:"+e.getMessage());
        }
    
        /**
         * 如果NullPointerException没用放到Retryable注解的value值内,获取到异常则不会重试,直接执行1次本方法。
         * @param e 异常
         */
        @Recover
        public void recover(NullPointerException e){
            log.warn("NPE最终处理的结果是:"+e.getMessage());
        }
    }
    
  3. 编写测试类

    import com.example.springcloud.ribbon.retry.service.UserService;
    import org.junit.jupiter.api.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest(classes = SpringcloudRibbonRetryApplication.class)
    class SpringcloudRibbonRetryApplicationTests {
    
        @Autowired
        private UserService userService;
    
        /**
         * spring原生retry测试
         * @throws Exception
         */
        @Test
        void retryTest() throws Exception {
            userService.run();
        }
    }
    
  4. 运行查看控制台:因为NullPointerException没有被配置Retryable所以发生异常时就直接调用public void recover(NullPointerException e)方法了。

    2021-06-19 06:12:12.402  INFO 77851 --- [           main] c.e.s.ribbon.retry.service.UserService   : --------run-------------
    2021-06-19 06:12:12.402  WARN 77851 --- [           main] c.e.s.ribbon.retry.service.UserService   : NPE最终处理的结果是:执行逻辑空指针了...
    
  5. 接下来修改一下UserService@Retryable值,将NullPointerException也加到配置中,如下:

    @Retryable(
      value = {RemoteAccessException.class,NullPointerException.class},// 发生这些异常进行重试
      maxAttempts = 5,// 重试次数,默认3次
      backoff = @Backoff(delay = 2000L,multiplier = 1) // 延迟3秒进行重试,单线程处理
    )
    
  6. 再次运行测试类:可以看到共进行了5次重试,每隔2秒1次,并且在最后一次重试后调用了reconver()方法。

    2021-06-19 06:16:38.956  INFO 77944 --- [           main] c.e.s.ribbon.retry.service.UserService   : --------run-------------
    2021-06-19 06:16:40.959  INFO 77944 --- [           main] c.e.s.ribbon.retry.service.UserService   : --------run-------------
    2021-06-19 06:16:42.961  INFO 77944 --- [           main] c.e.s.ribbon.retry.service.UserService   : --------run-------------
    2021-06-19 06:16:44.963  INFO 77944 --- [           main] c.e.s.ribbon.retry.service.UserService   : --------run-------------
    2021-06-19 06:16:46.968  INFO 77944 --- [           main] c.e.s.ribbon.retry.service.UserService   : --------run-------------
    2021-06-19 06:16:46.968  WARN 77944 --- [           main] c.e.s.ribbon.retry.service.UserService   : NPE最终处理的结果是:执行逻辑空指针了...
    

接下来我们看一下SpringCloud Ribbon的Retry实现。

SpringCloud Ribbon Retry配置

因为cloud的retry是基于HTTP接口进行重试的,所以只要在注册到eureka服务上进行调用即可。

基本原理:

  1. 假设有providerA服、providerB服务、consumerC服务、eurekaServer服务等4个服务。
  2. consumerC去调用providerA和providerB,因为做了负载均衡,不确定调用到哪个提供者。
  3. 我们给consumerC配置HTTP读取超时时间为2秒(2秒内不返回结果认为连接超时)
  4. 同时让其中一个提供者的接口休眠4秒,另一个提供者正常提供服务。
  5. 访问consumerC的接口来测试是否有重试效果。

注意在E版本之前,要防止hystix断路器超时时间导致retry失效。对于E版本之后,不需要考虑断路器的超时时间了

  1. 配置retry-service(消费端)的application.yml文件

    spring:
      application:
        name: retry-service # 服务名称
      cloud:
        loadbalancer:
          retry:
            enabled: true # 开启重试功能
    
    server:
      port: 7004 # 服务端口
      servlet:
        context-path: / 
    # Eureka的相关配置
    eureka:
      client:
        healthcheck:
          enabled: true  # 开启健康监测
        service-url:
          defaultZone: http://eureka1:8001/eureka  # 注册中心地址
    
    # 自定义配置:针对所有RestTemplate发起的HTTP请求做超时配置
    custom:
      request-factory:
        connect-timeout: 10000 # HTTP连接超时时间
        connection-request-timeout: 10000 # 请求连接超时时间 
        read-timeout: 2000  # HTTP读取时间,如果再2秒内没有得到结果,则认为超时
    
    # 针对某个微服务进行重试策略配置
    provider-service:
      ribbon:
        OkToRetryOnAllOperations: true # 对所有请求做重试
        MaxAutoRetriesNextServer: 1 # 切换实例次数
        MaxAutoRetries: 2 # 对当前实例进行重试次数
    
    
  2. 配置pom.xml

    <parent>
      <groupId>com.example</groupId>
      <artifactId>springcloud-ribbon</artifactId>
      <version>0.0.1-SNAPSHOT</version>
    </parent>
    
    <artifactId>springcloud-ribbon-retry</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
      </dependency>
      <!-- spring 原生retry组件 -->
      <dependency>
        <groupId>org.springframework.retry</groupId>
        <artifactId>spring-retry</artifactId>
      </dependency>
    </dependencies>
    
    <dependencyManagement>
      <dependencies>
        <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-dependencies</artifactId>
          <!--   2.3.8版本boot支持cloud Hoxton.SR11 版本,更高的boot需要cloud Hoxton 以上才可 ,详情参考spring.io -->
          <version>Hoxton.SR11</version>
          <type>pom</type>
          <scope>import</scope>
        </dependency>
      </dependencies>
    </dependencyManagement>
    
    <build>
      <finalName>springcloud-ribbon-retry</finalName>
      <plugins>
        <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
      </plugins>
    </build>
    
  3. 添加启动类

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.context.annotation.Bean;
    import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
    import org.springframework.retry.annotation.EnableRetry;
    import org.springframework.web.client.RestTemplate;
    
    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableRetry // 原生spring retry组件
    public class SpringcloudRibbonRetryApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringcloudRibbonRetryApplication.class, args);
        }
    
        @Bean // 交给spring管理
        @LoadBalanced // 开启负载均衡
        public RestTemplate restTemplate(){
            return new RestTemplate(httpComponentsClientHttpRequestFactory());
        }
    
        /**
         * 读取自定义配置
         * @return HttpComponentsClientHttpRequestFactory
         */
        @ConfigurationProperties(prefix = "custom.request-factory")
        @Bean
        public HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory(){
            return new HttpComponentsClientHttpRequestFactory();
        }
    
    }
    
  4. 编写API测试接口

    import com.alibaba.fastjson.JSON;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;
    
    @Slf4j
    @RestController
    public class RetryController {
    
        @Autowired
        private RestTemplate restTemplate;
    
        /**
         * 调用provider-service
         * @return
         */
        @GetMapping("retry")
        public String retry(){
            User user = restTemplate.getForObject("http://provider-service/getUser?id={1}", User.class,"001");
            log.info("调用service结果:{}", JSON.toJSONString(user));
            return "成功获取调用结果:"+user;
        }
    }
    
  5. 修改provider-service其中的一个接口(让方法耗时大于retry-service配置的HTTP读取超时时间,而触发重试机制去调用另一台provider-service )

    @GetMapping("getUser")
    public User getUser(@RequestParam String id) throws InterruptedException {
      log.info("provider-service ------getUser 获取用户信息--------id: {}",id);
      TimeUnit.SECONDS.sleep(3);// 休眠3秒,retry-service的HTTP readTimeout为2秒
      return new User("001","张三",18);
    }
    
  6. 启动测试

  1. 访问http://localhost:7004/retry 进行测试

    retry-service控制台打印:最终获取到了调用结果,但是浏览器等待很久(因为进行了多次重试)。

    2021-06-18 21:42:24.044  INFO 77045 --- [nio-7004-exec-1] c.netflix.config.ChainedDynamicProperty  : Flipping property: provider-service.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
    2021-06-18 21:42:24.064  INFO 77045 --- [nio-7004-exec-1] c.n.u.concurrent.ShutdownEnabledTimer    : Shutdown hook installed for: NFLoadBalancer-PingTimer-provider-service
    2021-06-18 21:42:24.064  INFO 77045 --- [nio-7004-exec-1] c.netflix.loadbalancer.BaseLoadBalancer  : Client: provider-service instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=provider-service,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
    2021-06-18 21:42:24.068  INFO 77045 --- [nio-7004-exec-1] c.n.l.DynamicServerListLoadBalancer      : Using serverListUpdater PollingServerListUpdater
    2021-06-18 21:42:24.085  INFO 77045 --- [nio-7004-exec-1] c.netflix.config.ChainedDynamicProperty  : Flipping property: provider-service.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
    2021-06-18 21:42:24.086  INFO 77045 --- [nio-7004-exec-1] c.n.l.DynamicServerListLoadBalancer      : DynamicServerListLoadBalancer for client provider-service initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=provider-service,current list of Servers=[172.19.137.36:7001, 172.19.137.36:7002],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone;	Instance count:2;	Active connections count: 0;	Circuit breaker tripped count: 0;	Active connections per server: 0.0;]
    },Server stats: [[Server:172.19.137.36:7002;	Zone:defaultZone;	Total Requests:0;	Successive connection failure:0;	Total blackout seconds:0;	Last connection made:Thu Jan 01 08:00:00 CST 1970;	First connection made: Thu Jan 01 08:00:00 CST 1970;	Active Connections:0;	total failure count in last (1000) msecs:0;	average resp time:0.0;	90 percentile resp time:0.0;	95 percentile resp time:0.0;	min resp time:0.0;	max resp time:0.0;	stddev resp time:0.0]
    , [Server:172.19.137.36:7001;	Zone:defaultZone;	Total Requests:0;	Successive connection failure:0;	Total blackout seconds:0;	Last connection made:Thu Jan 01 08:00:00 CST 1970;	First connection made: Thu Jan 01 08:00:00 CST 1970;	Active Connections:0;	total failure count in last (1000) msecs:0;	average resp time:0.0;	90 percentile resp time:0.0;	95 percentile resp time:0.0;	min resp time:0.0;	max resp time:0.0;	stddev resp time:0.0]
    ]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@5545ced6
    2021-06-18 21:42:25.073  INFO 77045 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty  : Flipping property: provider-service.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
    2021-06-18 21:42:31.145  INFO 77045 --- [nio-7004-exec-1] c.e.s.ribbon.retry.api.RetryController   : 调用service结果:{"age":18,"id":"001","name":"张三"}
    

    provider-service1控制台:进行了3次重试,但每次都超时。

    2021-06-18 21:42:24.112  INFO 76849 --- [nio-7001-exec-4] c.e.s.c.r.e.provider.api.UserController  : provider-service ------getUser 获取用户信息--------id: 001
    2021-06-18 21:42:26.120  INFO 76849 --- [nio-7001-exec-5] c.e.s.c.r.e.provider.api.UserController  : provider-service ------getUser 获取用户信息--------id: 001
    2021-06-18 21:42:28.122  INFO 76849 --- [nio-7001-exec-6] c.e.s.c.r.e.provider.api.UserController  : provider-service ------getUser 获取用户信息--------id: 001
    
    

    provider-service2控制台:被调用了1次,直接返回结果。

    2021-06-18 21:42:30.124  INFO 76767 --- [nio-7002-exec-6] c.e.s.c.r.e.p.second.api.UserController  : provider-second-service ------getUser 获取用户信息--------id: 001
    2021-06-18 21:42:47.104  INFO 76767 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints via configuration
    

    好了,springcloud的retry就搞定了!

    文中用到的代码示例下载:https://gitee.com/yunnasheng/springcloud-ribbon

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值