SpringCloud—Ribbon

简介

负载均衡在系统架构中是一个非常重要的内容,因为负载均衡是对系统的高可用、网络压力的缓解和处理能力扩容的重要手段之一。

Ribbon 是 Netflix 公司开源的一个负载均衡组件,Spring Cloud Ribbon基于Netflix Ribbon进行了封装,它只是一个工具类框架,不像服务注册中心、配置中心那样需要独立部署,但它让基于Spring Cloud构建的各个微服务之间使用 RestTemplate 或 Feign 调用http接口时可以轻松地实现客户端的负载均衡功能。

客户端负载均衡和服务端负载均衡

服务端负载均衡

通常我们所说的负载均衡都指的是服务端负载均衡,其中分为硬件负载均衡和软件负载均衡。
硬件负载均衡主要通过在服务器节点之间安装专门用于负载均衡的设备,比如F5等;软件负载均衡则是通过在服务器上安装一些用于负载均衡功能或模块等软件来完成请求分发工作,比如Nginx、Lvs等。
不论采用硬件负载均衡还是软件负载均衡,其示意图大概如下:

硬件负载均衡的设备或是软件负载均衡的软件模块都会维护一个可用的服务端清单,通过心跳检测等机制来剔除故障的服务端节点,以保证清单中都是可以正常访问的服务端节点。当客户端发送请求到负载均衡系统的时候,会按某种算法(比如线性轮询、按权重负载、按流量负载等)从维护的可用服务端清单中取出一台服务端端地址,然后进行转发。

客户端负载均衡

客户端负载均衡和服务端负载均衡最大的不同点在于上面所提到服务清单所存储的位置。在客户端负载均衡中,所有客户端节点都维护着自己要访问的服务端清单,而这些服务端端清单既可以通过配置文件指定,也可以通过注册中心来获取,比如Eureka、Nacos这些注册中心。客户端根据一定的负载均衡策略,直接决定调用某一台服务。

代码示例

思路:1.启用注册中心;2.创建服务提供者,服务提供者至少启动两个节点;3.创建服务消费者,以Feign客户端的方式调用服务提供者,验证ribbon组件是否以负载均衡的方式调用服务提供者。

启动eureka注册中心

参考Spring Cloud—Eureka注册中心一文。

创建服务提供者

  1. pom.xml文件引入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
  2. application.yml配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    server:
    port: 8011
    spring:
    application:
    name: ribbon-provider
    eureka:
    client:
    service-url:
    defaultZone: http://localhost:9110/eureka
    logging:
    level:
    root : info
  3. controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @RestController
    @RequestMapping(value = "/ribbonProvider", method = {RequestMethod.GET, RequestMethod.POST})
    public class RibbonProviderController {


    @RequestMapping(value = "/hello")
    public String hello(HttpServletRequest request) {
    int serverPort = request.getServerPort();
    return "hello, this is Ribbon Provider service, from port : " + serverPort;
    }

    }
  4. 启动类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @SpringBootApplication
    @EnableEurekaClient //注册到Eureka注册中心
    public class RibbonProviderBootstrap {

    public static void main(String[] args) {
    SpringApplication.run(RibbonProviderBootstrap.class, args);
    }

    }
  5. 将该服务打成jar包,指定不同的端口启用两个节点,访问一下看看是否正常。

    1
    2
    java -jar target/ribbon-provider-1.0-SNAPSHOT.jar --server.port=8011
    java -jar target/ribbon-provider-1.0-SNAPSHOT.jar --server.port=8012

    浏览器访问:

    结论:两个服务节点运行正常。

创建ribbon服务消费者

  1. 服务消费者比服务提供者多引入了ribbon和feign的依赖。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</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-openfeign</artifactId>
    </dependency>

    <dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency>
  2. application.yml配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    server:
    port: 8010
    spring:
    application:
    name: ribbon-consumer
    eureka:
    client:
    service-url:
    defaultZone: http://localhost:9110/eureka
    logging:
    level:
    root: info
  3. feign客户端

    1
    2
    3
    4
    5
    6
    7
    @FeignClient(name = "ribbon-provider", path = "/ribbonProvider")
    public interface RibbonProviderClient {

    @GetMapping("/hello")
    String hello();

    }
  4. controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @RestController
    @RequestMapping(value = "/ribbonConsumer", method = {RequestMethod.GET, RequestMethod.POST})
    public class RibbonConsumerController {

    @Autowired
    private RibbonProviderClient ribbonProviderClient;


    @RequestMapping(value = "/getHelloFromProvider")
    public String getHelloFromProvider() {
    return "getHelloFromProvider: " + ribbonProviderClient.hello();
    }


    }
  5. 启动类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @SpringBootApplication
    @EnableEurekaClient
    @EnableFeignClients
    public class RibbonConsumerBootstrap {

    public static void main(String[] args) {
    SpringApplication.run(RibbonConsumerBootstrap.class, args);
    }


    }
  6. 启动服务消费者,查看注册中心管理页面,可以看到服务提供者有两个节点分别运行在端口8011和8012,服务消费者有一个节点运行在8010端口。

验证

在浏览器中访问服务消费者的controller接口,可以看到consumer调用provider的接口时,采用的是轮询这种负载均衡的策略。

Ribbon的其它设置

  1. 负载均衡策略
    Ribbon默认是让客户端使用轮询的方式调用服务,如果想用使用其它的策略,只需要在项目中注入一个IRule实例。IRule是一个接口,它有很多具体的实现类,代表的是不同的策略。Ribbon共有7种策略,包括随机、轮询、最小并发、响应时间加权等,比如想要在项目中使用随机策略,只需加上如下配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /**
    * ribbon负载均衡配置
    */
    @Configuration
    public class RibbonLBConfig {

    //使用随机策略
    @Bean
    public IRule ribbonRule(){
    return new RandomRule();
    }
    }
  2. 超时与重试
    在application配置文件中配置如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    ribbon:
    # 同一实例最大重试次数,不包括首次调用
    MaxAutoRetries: 1

    # 重试其他实例的最大重试次数,不包括首次所选的server
    MaxAutoRetriesNextServer: 2

    # 是否所有操作都重试
    OkToRetryOnAllOperations: false

    # http socket 读取超时时间
    ReadTimeout: 10000

    # http连接超时时间
    ConnectTimeout: 10000
  3. RestTemplate使用Ribbon的负载均衡
    如果consumer端不是通过Feign来调用provider的服务,而是通过Spring自带的RestTemplate,则可以显示的配置一个RestTemplate实例,并且加上@LoadBalanced注解即可。

    1
    2
    3
    4
    5
    6

    @Bean
    @LoadBalanced //使RestTemplate以负载均衡的方式调用服务
    public RestTemplate restTemplate(){
    return new RestTemplate();
    }
------ 本文完 ------