SpringCloud(7)之Ribbon负载均衡

SpringCloud(7)之Ribbon负载均衡

微信搜索 zze_coding 或扫描 👉 二维码关注我的微信公众号获取更多资源推送:

介绍

SpringCloud Ribbon 是基于 Netflix Ribbon 实现的一套客户端负载均衡的工具。

Ribbon 是 Netflix 发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将 Netflix 的中间层服务连接在一起。Ribbon 客户端组件提供一系列完善的配置项如连接超时重试等。简单地说,就是在配置文件中列出 Load Balancer(简称 LB)后面所有的机器,Ribbon 会自动帮助你基于某种规则(如简单轮询、随机连接等)去连接这些机器。我们也很容易使用 Ribbon 实现自定义的负载均衡算法。

LB,即负载均衡(Load Balance),在微服务或分布式集群中经常用的一种应用。负载均衡简单地说就是将用户的请求平均的分配到多个服务上,从而达到系统的 HA。

常见的负载均衡方案有软件 Nginx、LVS,硬件 F5 等。

而 LB 又可以分为以下两种:

  • 集中式 LB:在服务的消费方和提供方之间使用独立的 LB 设施(可以是硬件,如 F5,也可以是软件,如 Nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方。

  • 进程内 LB:将 LB 逻辑集中到消费方,消费方从注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。Ribbon 就属于进程内 LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。

使用

1、修改消费者服务工程,对应占用端口为 80,添加依赖如下:

<!--ribbon 相关-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>

2、修改配置将消费者服务工程作为 Eureka 客户端注册到 Eureka 集群:

# application.yml
server:
  port: 80

eureka:
  client:
    service-url:
      defaultZone: http://www.eurekaserver1.com:7001/eureka,http://www.eurekaserver2.com:7002/eureka,http://www.eurekaserver3.com:7003/eureka
  instance:
    instance-id: microservicecloud-provider-dept
    prefer-ip-address: true # 访问路径显示 IP

spring:
  application:
    name: microservicecloud-consumer-dept

3、启用 Eureka 客户端功能:

// zze.springcloud.Application_80
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class Application_80 {

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

4、修改配置类,注册 RestTemplate bean 时添加注解启用负载均衡:

// zze.springcloud.cfgbeans.ConfigBean
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ConfigBean {
    @Bean
    @LoadBalanced // 客户端负载均衡
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

5、修改 Controller,修改微服务具体访问 URL 为微服务名称:

// zze.springcloud.controller.DeptController
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import zze.springcloud.entities.Dept;

import java.util.List;

@RestController
@RequestMapping("/consumer/dept")
public class DeptController {
    // 微服务 Provider 的服务地址
    // private static String REST_URL_PREFIX = "http://localhost:8001";

    // 要调用的微服务名称 即微服务工程的 spring.application.name 对应值
    private static String REST_URL_PREFIX = "http://microservicecloud-provider-dept";
    @Autowired
    private RestTemplate restTemplate;

    @PostMapping("/add")
    public boolean add(@RequestBody Dept dept) {
        return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class);
    }

    @GetMapping("/get/{id}")
    public Dept get(@PathVariable Long id) {
        return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class);
    }

    @GetMapping("/list")
    public List<Dept> list() {
        return restTemplate.getForObject(REST_URL_PREFIX + "/dept/list", List.class);
    }
}

到这里其实已经完成了负载均衡的基本配置,依次启动项目是可以正常访问的,但是因为提供者服务只有一个,看不出负载均衡的效果。

6、新建两个数据库,分别名为 "springcloud_8002" 和 "springcloud_8003",接着分别在两个数据库中执行下面 sql 脚本初始化表结构及数据:

-- dept.sql
CREATE TABLE dept
(
  dept_no   BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
  dept_name VARCHAR(60),
  db_source VARCHAR(60)
);

INSERT INTO dept(dept_name, db_source)
VALUES ('开发部', DATABASE());
INSERT INTO dept(dept_name, db_source)
VALUES ('人事部', DATABASE());
INSERT INTO dept(dept_name, db_source)
VALUES ('财务部', DATABASE());
INSERT INTO dept(dept_name, db_source)
VALUES ('市场部', DATABASE());
INSERT INTO dept(dept_name, db_source)
VALUES ('运维部', DATABASE());

SELECT * FROM dept;

7、新建两个子工程作为 Provider 服务工程与 "microservicecloud-provider-dept-8001" 提供相同服务做 Provider 服务集群,分别名为 "microservicecloud-provider-dept-8002" 和 "microservicecloud-provider-dept-8003",三个工程连接不同数据库,对应配置如下:

# application.yml#8001
server:
  port: 8001

mybatis:
  config-location: classpath:mybatis/mybatis.cfg.xml # mybatis 配置文件路径
  type-aliases-package: zze.springcloud.entities # 所有 Entity 别名类所在包
  mapper-locations:
    - classpath:mybatis/mapper/**/*.xml # mapper 映射文件

spring:
  application:
    name: microservicecloud-provider-dept # 当前微服务名称
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource # 数据源操作类型
    driver-class-name: org.gjt.mm.mysql.Driver # mysql 驱动包
    url: jdbc:mysql:///springcloud_8001 # 数据库连接 root
    username: root
    password: root
    dbcp2:
      min-idle: 5 # 数据库连接池的最小维持连接数
      initial-size: 5 # 初始化连接数
      max-total: 5 # 最大连接数
      max-wait-millis: 200 # 等待连接获取的最大超时时间

eureka:
  client: # 将当前工程作为 Eureka 客户端
    service-url:
      # 单机版
      # defaultZone: http://localhost:7001/eureka # Eureka 服务端地址
      defaultZone: http://www.eurekaserver1.com:7001/eureka,http://www.eurekaserver2.com:7002/eureka,http://www.eurekaserver3.com:7003/eureka
  instance:
    instance-id: microservicecloud-provider-dept-8001
    prefer-ip-address: true # 访问路径显示 IP

info:
  host: ${java.rmi.server.hostname}
  port: ${server.port}
  app.name: microservicecloud-provider-dept-8001
  build.artifactId: ${project.artifactId}
  build.version: ${project.version}
# application.yml#8002
server:
  port: 8002

mybatis:
  config-location: classpath:mybatis/mybatis.cfg.xml # mybatis 配置文件路径
  type-aliases-package: zze.springcloud.entities # 所有 Entity 别名类所在包
  mapper-locations:
    - classpath:mybatis/mapper/**/*.xml # mapper 映射文件

spring:
  application:
    name: microservicecloud-provider-dept # 当前微服务名称
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource # 数据源操作类型
    driver-class-name: org.gjt.mm.mysql.Driver # mysql 驱动包
    url: jdbc:mysql:///springcloud_8002 # 数据库连接 root
    username: root
    password: root
    dbcp2:
      min-idle: 5 # 数据库连接池的最小维持连接数
      initial-size: 5 # 初始化连接数
      max-total: 5 # 最大连接数
      max-wait-millis: 200 # 等待连接获取的最大超时时间

eureka:
  client: # 将当前工程作为 Eureka 客户端
    service-url:
      # 单机版
      # defaultZone: http://localhost:7001/eureka # Eureka 服务端地址
      defaultZone: http://www.eurekaserver1.com:7001/eureka,http://www.eurekaserver2.com:7002/eureka,http://www.eurekaserver3.com:7003/eureka
  instance:
    instance-id: microservicecloud-provider-dept-8002
    prefer-ip-address: true # 访问路径显示 IP

info:
  host: ${java.rmi.server.hostname}
  port: ${server.port}
  app.name: microservicecloud-provider-dept-8002
  build.artifactId: ${project.artifactId}
  build.version: ${project.version}
# application.yml#8003
server:
  port: 8003

mybatis:
  config-location: classpath:mybatis/mybatis.cfg.xml # mybatis 配置文件路径
  type-aliases-package: zze.springcloud.entities # 所有 Entity 别名类所在包
  mapper-locations:
    - classpath:mybatis/mapper/**/*.xml # mapper 映射文件

spring:
  application:
    name: microservicecloud-provider-dept # 当前微服务名称
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource # 数据源操作类型
    driver-class-name: org.gjt.mm.mysql.Driver # mysql 驱动包
    url: jdbc:mysql:///springcloud_8003 # 数据库连接 root
    username: root
    password: root
    dbcp2:
      min-idle: 5 # 数据库连接池的最小维持连接数
      initial-size: 5 # 初始化连接数
      max-total: 5 # 最大连接数
      max-wait-millis: 200 # 等待连接获取的最大超时时间

eureka:
  client: # 将当前工程作为 Eureka 客户端
    service-url:
      # 单机版
      # defaultZone: http://localhost:7001/eureka # Eureka 服务端地址
      defaultZone: http://www.eurekaserver1.com:7001/eureka,http://www.eurekaserver2.com:7002/eureka,http://www.eurekaserver3.com:7003/eureka
  instance:
    instance-id: microservicecloud-provider-dept-8003
    prefer-ip-address: true # 访问路径显示 IP

info:
  host: ${java.rmi.server.hostname}
  port: ${server.port}
  app.name: microservicecloud-provider-dept-8003
  build.artifactId: ${project.artifactId}
  build.version: ${project.version}

8、测试:

先启动占用端口 7001、7002、7003 的 EurekaServer 服务,

再启动占用端口 8001、8002、8003 的 Provider 服务,

最后启动占用端口 80 的消费者服务,随便访问一个 EurekaServer 的 WebUI 页面:

image.png

会发现消费者服务与提供者服务都注册到了 EurekaServer,并且提供者服务有三个实例分别占用端口 8001、8002、8003,

多次访问 http://localhost/consumer/dept/list,会发现每次访问返回的数据都是从不同的数据库返回,即负载均衡环境搭建成功。

Ribbon 在工作时分为两步走:

  1. 选择 EurekaServer,它优先选择在同一个区域内负载较少的 Server。
  2. 根据用户指定的策略,从 EurekaServer 取到的服务注册列表中选择一个服务。

其中 Ribbon 提供了多种策略,例如轮询(默认)、随机和根据响应时间加权。

若测试机器内存不足,则可以只启动一部分服务测试,例如可以只启动一个 EurekaServer,因为在 EurekaServer 集群中每一个 EurekaServer 都是平等的,还可以只启动两个提供者服务,能看到返回数据的不同即可,内存没有 16G 有点 hold 不住。

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://www.zze.xyz/archives/springcloud7.html

Buy me a cup of coffee ☕.