06-SpringCloud 之 Ribbon

负载均衡 (Load Balance) - LB

  • 集中式 LB:在服务的消费方和提供方之间,使用独立的 LB 设施,如 Nginx,由该设施负责把请求通过某种策略转发至服务提供方
  • 进程式 LB:将 LB 逻辑集成到消费方,消费方从注册中心获知有哪些地址可用,然后自行选择合适的服务器

什么是 Ribbon

Ribbon 即:客户端的负载均衡器,属于进程式 LB,它是一个类库,集成在消费方进程中,消费方通过它来获取服务提供方地址

创建多个服务提供者

每个提供者使用各自的数据库

  1. 新建 Module:spring-cloud-provider-dept-8002

  2. 编写 pom 引入依赖

    pom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>spring-cloud-netflix</artifactId>
            <groupId>com.kaishen</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>spring-cloud-provider-dept-8002</artifactId>
    
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
        </properties>
    
        <!-- 引入该模块所需依赖 -->
        <dependencies>
            <!-- api -->
            <dependency>
                <groupId>com.kaishen</groupId>
                <artifactId>spring-cloud-api</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
    
            <!-- junit -->
            <dependency>
                <groupId>org.junit.jupiter</groupId>
                <artifactId>junit-jupiter</artifactId>
                <scope>test</scope>
            </dependency>
    
            <!-- log4j -->
            <dependency>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-core</artifactId>
            </dependency>
    
            <!-- logback -->
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-core</artifactId>
            </dependency>
    
            <!-- lombok -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
    
            <!-- SpringBoot Web -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <!-- SpringBoot 监控 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
    
            <!-- DataSource -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
            </dependency>
    
            <!-- MySQL -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
    
            <!-- MyBatis -->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
            </dependency>
    
            <!-- Eureka 服务提供方引入 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-eureka</artifactId>
            </dependency>
        </dependencies>
    </project>
    
  3. 编写 application.yml

    application.yml
    server:
      port: 8002
    
    # Spring 配置
    spring:
      application:
        name: spring-cloud-provider-dept
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource # 数据源
        driver-class-name: org.gjt.mm.mysql.Driver # 驱动
        url: jdbc:mysql://localhost:3306/db02?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
        username: root
        password: 123456
    
    # MyBatis 配置
    mybatis:
      mapper-locations: classpath:mybatis/mappings/*.xml
      type-aliases-package: com.kaishen.pojo
    
    # 监控信息配置
    info:
      app.name: kaishen-spring-cloud-dept8002
    
    # Eureka 配置
    eureka:
      client:
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
      instance:
        instance-id: spring-cloud-provider-dept8002 # 服务描述信息
        prefer-ip-address: true # 显示 ip 地址
    
  4. 编写启动类,开启注解,注册到 Eureka

    DeptProviderApplication8002
    package com.kaishen;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.cloud.netflix.hystrix.EnableHystrix;
    
    /**
     * 部门服务启动类
     * @author : toby
     * Create in 0:33 2022/5/9
     */
    @SpringBootApplication
    @EnableEurekaClient
    public class DeptProviderApplication8002 {
    
        public static void main(String[] args) {
            SpringApplication.run(DeptProviderApplication8002.class, args);
        }
    }
    
  5. 编写业务功能,新建 dao、mapper、service、controller 等

  6. 重复以上 1~5 步,创建多个提供者,注意端口号、数据库连接

Ribbon 自带的常用负载均衡算法

  • AvailabilityFilteringRule:可用性过滤规则,先过滤掉跳闸的服务,再轮询
  • BestAvailableRule:最优规则,过滤掉跳闸的服务,再选择并发量最小的
  • RandomRule:随机策略
  • RetryRule:重试策略
  • RoundRobinRule:轮询调度规则
  • WeightedResponseTimeRule:加权响应时间规则,每 30s 计算一次服务器响应时间,响应时间越短的服务器权重越高

Ribbon 实现默认负载均衡

  1. 新建 Module:spring-cloud-consumer-dept-80

  2. 编写 pom 引入依赖

    pom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>spring-cloud-netflix</artifactId>
            <groupId>com.kaishen</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>spring-cloud-consumer-dept-80</artifactId>
    
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
        </properties>
    
        <!-- 引入该模块所需依赖 -->
        <dependencies>
            <!-- api -->
            <dependency>
                <groupId>com.kaishen</groupId>
                <artifactId>spring-cloud-api</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
    
            <!-- lombok -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
    
            <!--SpringBoot Web-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <!-- SpringBoot 监控 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
    
            <!-- Eureka Client -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-eureka</artifactId>
            </dependency>
    
            <!-- Ribbon -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-ribbon</artifactId>
            </dependency>
        </dependencies>
    </project>
    
  3. 编写 application.yml,配置注册中心

    application.yml
    server:
      port: 80
    
    # Eureka 配置
    eureka:
      client:
        register-with-eureka: false # Consumer 无需注册
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
    
  4. 创建启动类,使用默认负载均衡策略,不需要加 @RibbonClient 注解

    DeptConsumerApplication80
    package com.kaishen;
    
    import com.ribbon.config.KaiShenRule;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.cloud.netflix.ribbon.RibbonClient;
    
    /**
     * 部门服务消费者启动类
     *  RibbonClient configuration = KaiShenRule.class ==> 启动时扫描 KaiShenRule
     * @author : toby
     * Create in 7:43 2022/5/9
     */
    @SpringBootApplication
    @EnableEurekaClient
    @RibbonClient(name = "SPRING-CLOUD-PROVIDER-DEPT", configuration = KaiShenRule.class)
    public class DeptConsumerApplication80 {
    
        public static void main(String[] args) {
            SpringApplication.run(DeptConsumerApplication80.class, args);
        }
    }
    
  5. 编写配置类,配置 RestTemplate 并开启负载均衡注解 @LoadBalanced

    ConfigBean
    package com.kaishen.config;
    
    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;
    
    /**
     * LoadBalanced 注解,默认使用过滤轮询策略实现负载均衡,此时启动类无需添加 @RibbonClient 注解
     * @author : toby
     * Create in 7:44 2022/5/9
     */
    @Configuration
    public class ConfigBean {
    
        @Bean
        @LoadBalanced
        public RestTemplate getRestTemplate() {
            return new RestTemplate();
        }
    }
    
  6. 编写控制层,通过 RestTemplate 实现远程服务调用

    DeptConsumerController
    package com.kaishen.controller;
    
    import com.kaishen.pojo.CommonResult;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;
    
    import javax.annotation.Resource;
    
    /**
     * 部门服务消费者控制层
     * @author : toby
     * Create in 7:46 2022/5/9
     */
    @RestController
    @Slf4j
    public class DeptConsumerController {
    
        /**
         * 微服务前缀
         */
        private static final String REST_URL_PREFIX = "http://SPRING-CLOUD-PROVIDER-DEPT";
    
        @Resource
        private RestTemplate restTemplate;
    
        /**
         * 根据部门编号查询部门信息
         * @param id 部门编号
         * @return 返回前端所需信息
         */
        @RequestMapping("/consumer/dept/get/{id}")
        public CommonResult queryDeptById(@PathVariable Long id) {
            // RestTemplate 实现远程调用
            CommonResult result = restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, CommonResult.class);
    
            log.info("查询结果:" + result);
    
            return result;
        }
    }
    

Ribbon 实现自定义负载均衡策略

  1. 在实现默认负载均衡基础上,新建 KaiShenRandomRule 类,继承 AbstractLoadBalancerRule 并重写其 choose 方法,此处摘抄随机算法源码实现

    KaiShenRandomRule
    package com.ribbon.rules;
    
    import com.netflix.client.config.IClientConfig;
    import com.netflix.loadbalancer.AbstractLoadBalancerRule;
    import com.netflix.loadbalancer.ILoadBalancer;
    import com.netflix.loadbalancer.Server;
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.List;
    import java.util.concurrent.ThreadLocalRandom;
    
    /**
     * 自定义负载均衡策略
     *  重写 choose 方法
     * @author : toby
     * Create in 10:22 2022/5/9
     */
    @Slf4j
    public class KaiShenRandomRule extends AbstractLoadBalancerRule {
    
        @Override
        public void initWithNiwsConfig(IClientConfig clientConfig) {
    
        }
    
        /**
         * RandomRule 原码
         * @param key key
         * @return 返回可用的服务
         */
        @Override
        public Server choose(Object key) {
            log.info("key: " + key);
    
            // 获取当前使用的负载均衡器
            ILoadBalancer loadBalancer = this.getLoadBalancer();
    
            if (null == loadBalancer) {
                log.info("没有可用的负载均衡器");
                return null;
            }
    
            Server server = null;
    
            while (server == null) {
                if (Thread.interrupted()) {
                    return null;
                }
    
                // 获取在线的服务和全部服务
                List<Server> upList = loadBalancer.getReachableServers();
                List<Server> allList = loadBalancer.getAllServers();
    
                log.info("在线服务:" + upList + ";总服务:" + allList);
    
                int serverCount = allList.size();
                if (serverCount == 0) {
                    return null;
                }
    
                // 获取总服务数内的随机数
                int index = ThreadLocalRandom.current().nextInt(serverCount);
    
                log.info("随机数:" + index);
    
                // 从在线的服务列表中随机获取指定下标的服务
                server = upList.get(index);
    
                if (server ==  null) {
                    // 如果没有获取到,则可能是该服务宕机了,暂时挂起
                    Thread.yield();
                    continue;
                }
    
                if (server.isAlive()) {
                    return server;
                }
    
    //            server = null;
                Thread.yield();
            }
    
            return server;
        }
    }
    
  2. 编写配置类,此类不能与启动类在同一包下,否则会被所有 Ribbon Client 共享

    KaiShenRule
    package com.ribbon.config;
    
    import com.netflix.loadbalancer.IRule;
    import com.ribbon.rules.KaiShenRandomRule;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * 规则配置类
     * @author : toby
     * Create in 10:06 2022/5/9
     */
    @Configuration
    public class KaiShenRule {
    
        @Bean
        public IRule myRule() {
            return new KaiShenRandomRule();
        }
    }
    
  3. 启动类开启注解 @RibbonClient(name = "SPRING-CLOUD-PROVIDER-DEPT", configuration = KaiShenRule.class)

posted @ 2022-05-28 15:17  kaishen  阅读(27)  评论(0)    收藏  举报