四、ribbon

负载均衡(Load Balancer,简称LB):其含义就是指将负载(客户端发送的请求、工作任务等)进行平衡、分摊到多个服务上处理,从而协同完成工作任务。

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡工具,它将提供一系列完整的配置项如:连接超时、重试等等,简单的说,就是在配置文件中列出LB后面所有的机器,Ribbon会自动基于某种规则(如简单轮询、随即连接等等)去连接这些机器。我们也可以更容易使用Ribbon实现自定义的负载均衡算法

  • ribbon作用:
  • 将可用的服务列表返回给客户端
  • 核心:将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)
  • 常用的负载均衡软件有Nginx、Lvs等
  • dubbo、Spring Cloud中均提供了负载均衡,Spring Cloud的负载均衡算法可以自定义
  • 负载均衡简单分类
  • 集中式LB
    即在服务的消费方和提供方之间使用独立的LB设施,如Nginx(反向代理服务器),由该设施负责把访问请求通过某种策略转发至服务提供方
  • 进程式LB
    将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可以用,然后自己再从这些地址中选出一个合适的服务器
    Ribbon属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址

4.1 Ribbon使用(单服务提供者)

  • 在相应的消费者module中添加依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-ribbon</artifactId>
    <version>1.4.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
    <version>1.4.6.RELEASE</version>
</dependency>
  • 编写客户端配置文件
# Eureka 配置
eureka:
  client:
    register-with-eureka: false # 不向Eureka注册自己,消费者只需要去注册中心拿服务
    service-url:
      # 客户端可以从以下三个注册中心中随机选择一个,取出服务
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  • 在主启动类中添加开启Eureka注解
@EnableEurekaClient
  • 在configBean中添加负载均衡注解
// 配置实现负载均衡的RestTemplate
@Bean
@LoadBalanced // Ribbon
public RestTemplate getRestTemplate() {
    return new RestTemplate();
}

**注意:**使用了Ribbon做负载均衡,此时消费者请求的url应该是一个变量,变量名为需要调用的服务的名称(即注册中心所显示的服务名,本例中服务只有一个,为SPRINGCLOUD-PROVIDER-DEPT),因此需要对消费者的controller层做出改变,将原来的http://localhost:8001更换成对应的服务名

// private static final String REST_URL_PREFIX = "http://localhost:8001";
 // 使用Ribbon做负载均衡时,消费者请求的地址应该是一个变量,该变量即服务的名称
 private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT";

启动注册中心、服务提供者、消费者,在注册中心可以看到服务已注册

nacos springcloud 负载均衡 服务_自定义

消费者可成功调用服务

nacos springcloud 负载均衡 服务_自定义_02

结论:Ribbon和Eureka整合以后,客户端可以直接调用服务,不用关心注册中心的端口号和IP地址

4.2 Ribbon使用(多服务提供者)

nacos springcloud 负载均衡 服务_负载均衡_03

  • 创建三个不同的数据库db01、db02、db03
  • 复制得到另外两个服务提供者8002、8003,服务提供者的功能均相同,且服务名称也相同(名称在如下yml文件中进行配置)
spring:
  application:
    name: springcloud-provider-dept
  • 启动3个服务提供者、两个注册中心、一个消费者

nacos springcloud 负载均衡 服务_spring_04

消费者每次发出请求localhost:81/consumer/dept/list,都会调用到不同的数据库,由于默认采用轮询的负载均衡方式,因此调用顺序为db01、db02、db03

4.3 自定义负载均衡策略

与springcloud包同级的位置建立配置包myrule,以建立自定义Ribbon类,并在启动类中添加注解@RibbonClient,configuration = MyRule.class表示在微服务启动的时候,会加载自定义的Ribbon类

package com.mark.springcloud;/*
 * @project: springcloud
 * @name: DeptConsumer_80
 * @author: Mark
 * @Date: 2021/7/22
 * @Time: 23:00
 */

import com.mark.myrule.MyRule;
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;

@SpringBootApplication
@EnableEurekaClient
// 对名称为SPRINGCLOUD-PROVIDER-DEPT的服务进行均衡
@RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT", configuration = MyRule.class)
public class DeptConsumer_81 {
    public static void main(String[] args) {
        SpringApplication.run(DeptConsumer_81.class, args);
    }
}

在myrule包下新建自定义配置类、负载均衡策略类

配置类

package com.mark.myrule;/*
 * @project: springcloud
 * @name: MyRule
 * @author: Mark
 * @Date: 2021/7/24
 * @Time: 21:03
 */

import com.netflix.loadbalancer.IRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyRule {

    @Bean
    public IRule myRandomRule() {
        return new MarkRandomRule(); // 默认为轮询,现自定义为MarkRandomRule()
    }
}

负载均衡策略类(这一程序主要是从自带的均衡算法RandomRule()更改得来)

package com.mark.myrule;/*
 * @project: springcloud
 * @name: MarkRandomRule
 * @author: Mark
 * @Date: 2021/7/24
 * @Time: 21:21
 */


import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;

import java.util.List;
import java.util.concurrent.ThreadLocalRandom;


public class MarkRandomRule extends AbstractLoadBalancerRule {

    // 每个服务,访问5次,5次后换下一个服务,总共3个服务
    private int total = 0; // 被调用的次数
    private int currentIndex = 0; // 当前是谁在提供服务

//    @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        }
        Server server = null;

        while (server == null) {
            if (Thread.interrupted()) {
                return null;
            }
            List<Server> upList = lb.getReachableServers(); // 获取活着的服务
            List<Server> allList = lb.getAllServers(); // 获取所有服务

            int serverCount = allList.size();
            if (serverCount == 0) {
                return null;
            }

//            int index = chooseRandomInt(serverCount); // 生存区间随机数
//            server = upList.get(index); // 从活着的服务中随机获取一个

            // ==========================================
            if (total < 5) {
                server = upList.get(currentIndex); // 从活着的服务中,获取指定的服务
                total++;
            } else {
                total = 0;
                currentIndex++;
                if (currentIndex > upList.size()) { // 如果服务索引大于了活服务的数量
                    currentIndex = 0; // 重新从第一个服务开始调用
                }
                server = upList.get(currentIndex);
            }
            // ==========================================

            if (server == null) {
                Thread.yield();
                continue;
            }

            if (server.isAlive()) {
                return (server);
            }

            server = null;
            Thread.yield();
        }

        return server;

    }

    protected int chooseRandomInt(int serverCount) {
        return ThreadLocalRandom.current().nextInt(serverCount);
    }

    @Override
    public Server choose(Object key) {
        return choose(getLoadBalancer(), key);
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
        // TODO Auto-generated method stub

    }
}

访问路径 http://localhost:81/consumer/dept/list 可成功调用服务,并且当访问同一服务的次数大于5次后,将切换服务,与自定义负载均衡策略相符

前5次访问的结果

nacos springcloud 负载均衡 服务_负载均衡_05

第6次的访问结果

nacos springcloud 负载均衡 服务_spring_06