SpringCloud学习——Ribbon负载均衡

简介

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

初步配置

前面一直强调,Ribbon 是基于客户端的负载均衡,所以们需要对消费者端做手脚,而不是服务提供端。在之前的博客上已经配置好了消费者服务,现在对其进行更改。

第一步

在消费者的pom.xml中添加相关依赖,因为Ribbon要和Eureka整合才能发挥强大的功能,所以在引入 Ribbon 依赖的同时,也要引入 Eureka 的依赖。此时这个消费者,也是一个 Eureka 的客户端了,但是它只需要消费,不需要注册到注册中心。

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
        </dependency>

第二步

修改消费者微服务的 application.properties 配置文件,需要加上注册中心的地址。现在消费者不直接去调用服务提供者了,而是去注册中心找注册了的服务,再通过路径匹配,Ribbon转发到相应的请求,最终返回数据。Eureka集群已经在之前的博客中配好了。
配置如下:

server.port=80
# 消费者消费即可,不用注册到注册中心
eureka.client.register-with-eureka=false
# 注册中心地址
eureka.client.service-url.defaultZone=http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka

第三步

集成了Ribbon之后,我们访问微服务,可以不再去关心 ip + port了,我们可以直接通过微服务名称去调用。之前在建立消费者模块的时候,采用的是 RestTemplate 来实现不同微服务之间的调用,我们需要做如下设置:

	@Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

在注入Bean的方法上加上 @LoadBalanced 注解。随后在消费者的controller中,将原来的URL替换成微服务的名称。这个注解也是开启负载均衡效果的注解,下面再说。

//    private static final String REST_URL = "http://localhost:8001";之前的
//  使用Ribbon之后,可以直接通过微服务的名称进行访问
    private static final String REST_URL = "http://SPRINGCLOUDSERVICE-USER";

因为消费者也集成了 eureka-client,消费者也是一个eureka的客户端。所以要在主启动类上加上 @EnableEurekaClient 注解。

@SpringBootApplication
@EnableEurekaClient
public class UserConsumer80_App {
    public static void main(String[] args) {
        SpringApplication.run(UserConsumer80_App.class, args);
    }
}

第四步

启动eureka集群,启动服务提供者,启动消费者服务。进行测试。
访问 http://localhost/user/get 路径,成功得到数据。
在这里插入图片描述
Ribbon和Eureka整合后Consumer可以直接调用服务而不用再关心地址和端口号

负载均衡

在初步配置中,实现了直接通过微服务的名字来访问服务的微服务的提供者,而不用再去关心提供者的 ip + port,这也是整合Ribbon带来的一个好处。接下来要将负载均衡的效果体现出来。那么此时一个provider就不行了,因为只有一个,无论多少请求过来,都会找到这个 provider 服务。所以再创建两个provider微服务。

创建两个微服务 springcloudservice-provider-user-8002、springcloudservice-provider-user-8003,provider的搭建在之前的博客搭建好了,这两个微服务的配置文件大致与 springcloudservice-provider-user-8001 相同,可以拷贝过来进行一定的修改。

第一步

配置 pom.xml 文件。

springcloudservice-provider-user-8002 & springcloudservice-provider-user-8003的 pom.xml 如下:

<dependencies>
       <dependency>
           <groupId>org.example</groupId>
           <artifactId>springcloudservice-api</artifactId>
           <version>${project.version}</version>
       </dependency>
       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
       </dependency>
       <dependency>
           <groupId>com.alibaba</groupId>
           <artifactId>druid</artifactId>
       </dependency>
       <dependency>
           <groupId>org.mybatis.spring.boot</groupId>
           <artifactId>mybatis-spring-boot-starter</artifactId>
       </dependency>
       <dependency>
           <groupId>junit</groupId>
           <artifactId>junit</artifactId>
       </dependency>
       <dependency>
           <groupId>ch.qos.logback</groupId>
           <artifactId>logback-core</artifactId>
       </dependency>
       <dependency>
           <!--使用内嵌的jetty-->
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-jetty</artifactId>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
       </dependency>

       <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-eureka</artifactId>
       </dependency>
       <!-- actuator监控信息完善 -->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-actuator</artifactId>
       </dependency>
   </dependencies>

第二步

为每个微服务都创建一个对应的数据库。

以前的单体架构可能一个项目就是一个数据库,里面多张表。而现在微服务架构,每个微服务都可以拥有自己独立的数据库。

建库建表语句

CREATE DATABASE cloudDB02;

USE cloudDB02;

CREATE TABLE `user`(
	`id` INT PRIMARY KEY AUTO_INCREMENT,
	`name` VARCHAR(5),
	`db_source` VARCHAR(20)
);

INSERT INTO `user`(`name`,`db_source`) VALUES('张三',DATABASE());
INSERT INTO `user`(`name`,`db_source`) VALUES('李四',DATABASE());
INSERT INTO `user`(`name`,`db_source`) VALUES('王五',DATABASE());
INSERT INTO `user`(`name`,`db_source`) VALUES('赵六',DATABASE());
INSERT INTO `user`(`name`,`db_source`) VALUES('老七',DATABASE());

SELECT * FROM `user`;

第三步

修改各自的 application.properties 配置文件

server.port=8002

spring.datasource.url=jdbc:mysql://120.77.41.74/cloudDB01?characterEncoding=utf8&useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

mybatis.config-location=classpath:/mybatis/mybatis.cfg.xml
mybatis.type-aliases-package=com.zxb.springcloud.bean
mybatis.mapper-locations=classpath:/mybatis/mapper/*.xml

spring.application.name=springcloudservice-user
eureka.client.service-url.defaultZone=http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
eureka.instance.instance-id=springcloudservice-user
eureka.instance.prefer-ip-address=true

info.app.name=springcloudservice2
info.company.name=www.zxb.com
info.build.artifactId=${project.artifactId}
info.build.version=${project.version}

第四步

测试

启动 Eureka-Server 集群,然后再启动刚刚配置的两个微服务,访问接口,看是否连通。

8002正常:
在这里插入图片描述
8003正常
在这里插入图片描述

第五步

测试负载均衡功能

上面提到过,Ribbon 是基于 客户端 的负载均衡工具,所以要将消费者微服务和Ribbon整合。消费者去调用服务提供者的时候,使用了 RestTemplate 来进行微服务之间的调用。而要实现负载均衡功能,要在创建 RestTemplate 的方法上加上 @LoadBalance 注解。有了这个注解,调用微服务可以直接通过服务名来访问,并且实现了负载均衡的效果。

	@Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

配置完毕后,启动 Eureka Server 集群。启动三个服务提供者,启动消费者,进行最终的测试。

先来到注册中心页面:

此时相当于我们的一个微服务,有三个实例。
在这里插入图片描述
接下来测试消费者来调用接口,因为消费者端配置了负载均衡,看看效果。

一直发送同一个请求:

第一次发送
在这里插入图片描述
第二次发送
在这里插入图片描述
第三次发送
在这里插入图片描述
第四次发送结果又变成 cloudDB01。这就是 Ribbon 提供的负载均衡效果。

我们发送请求,由于我们是带了客户端的负载均衡, @LoadBalance,所以会去使用默认的轮询算法去访问 provider 微服务。SpringCloud帮我们做到了一个注解就实现了负载均衡的效果,这比 Nginx 的配置文件写起来方便多了。

在这里插入图片描述

修改轮询算法

切换Ribbon自带的轮询算法

之前负载均衡采用的是默认的轮询算法,是Ribbon出厂自带的,而Ribbon出厂自带了7中算法。

RoundRobinRule轮询
RandomRule随机
AvailabilityFilteringRule会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问
WeightedResponseTimeRule根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高。刚启动时如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够,会切换到WeightedResponseTimeRule
RetryRule先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务
BestAvailableRule会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
ZoneAvoidanceRule默认规则,复合判断server所在区域的性能和server的可用性选择服务器

而我们要切换轮询算法,需要用到 Ribbon 中的核心组件 IRule

IRule 是一个接口。 位于com.netflix.loadbalancer包下。
在这里插入图片描述
在我这个版本中,该接口中共有三个方法:

public interface IRule{

    public Server choose(Object key);
    
    public void setLoadBalancer(ILoadBalancer lb);
    
    public ILoadBalancer getLoadBalancer();    
}

IRule 接口根据特定算法中从服务列表中选取一个要访问的服务。这个接口中,有一个getLoadBalancer()方法,返回是一个ILoadBalancer实现类。而这个ILoadBalancer,就是定义软件负载均衡操作的接口,它有一个实现类就是 AbstractLoadBalancerRule,这七大负载均衡算法接继承了 AbstractLoadBalancerRule类。
在这里插入图片描述

想要切换Ribbon的负载均衡算法,可以写一个配置类,创建一个 IRule 类型的bean,返回对应的轮询算法实现类即可。

	@Bean
    public IRule myRule() {
        return new RandomRule(); // 使用我随机选择算法替换默认的轮询算法
    }

此时重启消费者服务,然后访问同一接口,发现就是随机的了,不再是按照轮询的方式。

自定义轮询算法

如果不想使用Ribbon出厂自带的负载均衡算法,我们也可以自定义轮询算法。自定义轮询算法,必须将我们写的类去继承 AbstractLoadBalancerRule抽象类。

假设现在有一个需求:依旧是轮询策略,但是加上新需求,每个服务器要求被调用五次。也即以前是轮询时每台机子一次,现在改为每台机子五次五次的轮询。就像是每人值日一天轮着来,现在是每人值日一周,轮着来。

那么我们就可以先去查看下源码,去学习借鉴下。以下是随机负载均衡的算法:


package com.netflix.loadbalancer;

import java.util.List;
import java.util.Random;

import com.netflix.client.config.IClientConfig;


public class RandomRule extends AbstractLoadBalancerRule {
    Random rand;

    public RandomRule() {
        rand = new Random();
    }

    /**
     * Randomly choose from all living servers
     */
    @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 = rand.nextInt(serverCount);
            server = upList.get(index);

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

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

        return server;

    }

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

	@Override
	public void initWithNiwsConfig(IClientConfig clientConfig) {
		
	}
}

可以看到,真正起作用的,还是 choose 方法,那么我们就可以将源码复制过来,再按照我们的业务逻辑进行修改。

代码如下:

package com.zxb.myrule;

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.Random;

/**
 * 我自定义的负载均衡算法,需求就是每台机子轮流调用五次
 */
public class RandomRule_ZXB extends AbstractLoadBalancerRule {

    // 当 total到5时,需要重新置为0,达到过一次5次,index就要加一,轮到下一个服务
    private int total = 0; // 总共被调用的次数,要求每台被调用 5 次
    private int currentIndex = 0; // 当前提供服务的机器号

    // ILoadBalancer 表示负载均衡算法
    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(); // 微服务所有的实例(包括down的)

            int serverCount = allList.size();
            if (serverCount == 0) { // 判断个数
                return null;
            }

            // 算法逻辑
            if (total < 5) {
                server = upList.get(currentIndex);
                total++;
            } else {
                total = 0;
                currentIndex++;
                if (currentIndex >= upList.size()) {  // 注意是 >=
                    currentIndex = 0;
                }
                // server = upList.get(currentIndex); // 不用写这个,因为是 while 循环,
                // 如果 server == null 成立,仍会进入if 判断
            }


            if (server == null) {
                /*
                 * The only time this should happen is if the server list were
                 * somehow trimmed. This is a transient condition. Retry after
                 * yielding.
                 */
                Thread.yield(); // 休息一会
                continue; // 继续
            }

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

        return server;

    }

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

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

    }
}

自定义负载均衡算法完成后,要将我们自定义的类加入到IOC容器当中,但是我们写的类,不能和启动类放在同一目录下!
官方文档也给出了警告:
在这里插入图片描述
所以目录结构应该这样:
在这里插入图片描述
然后在配置类中将我们写的自定义类加入到IOC容器当中。

@Configuration
public class RuleConfig {
    @Bean
    public RandomRule_ZXB myRule() {
        return new RandomRule_ZXB();
    }
}

然后,在消费者的主启动类上加上 @RibbonClient 注解,并给出提供者的微服务名以及配置类。

@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "SPRINGCLOUDSERVICE-USER",configuration = {RuleConfig.class})
public class UserConsumer80_App {
    public static void main(String[] args) {
        SpringApplication.run(UserConsumer80_App.class, args);
    }
}

而只有加了 @RibbonClient 注解,那么在启动该微服务的时候,就能去加载我们自定义的Ribbon配置类,从而使配置类生效。

这样我们去访问接口,就会按照我们的算法来了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值