SpringCloud初级学习(四)------Ribbon负载均衡

SpringCloud初级学习(四)------Ribbon负载均衡

前言

在前面的博文中,我们创建了helloword-cloud的工程来使用了Eureka的服务注册,现在,我们的服务已经注册到Eureka了,接下来我们来使用消费者访问服务,在这里SpringCloud为我们提供了Ribbon来对客户端的请求进行负载均衡.

正文

1. Ribbon是什么?

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

它的主要功能是提供客户端的负载均衡,简单来说,就是帮助我们基于某种规则,如轮询或者随机,去连接对应的微服务,也可以通过Ribbon实现自定义的负载均衡算法.

所谓负载均衡,就是将用户的请求平摊到多个服务上.

常见的负载均衡有Nginx、F5等,在dubbo中也有对应的负载均衡的实现.

2. Ribbon使用

现在的情况是,我们的微服务helloword-cloud-provider已经部署成功,并且注册到Eureka上了,但是现在只有一个服务的提供者,我们可以新建一个和helloword-cloud-provider一样的SpringBoot工程作为第二个微服务提供者,姑且将其命名为:helloword-cloud-provider8002.后面的数字代表该服务启动的端口号.而之前的provider的端口号是8001.

下面是新建服务提供者8002的步骤

我们新建一个provider,命名为provider8002,启动端口是8002,区别在于,8002连接的是自己的数据库,读数据也是读自己的数据表.下面是SQL脚本:

DROP DATABASE IF EXISTS cloudDB02;
 
CREATE DATABASE cloudDB02 CHARACTER SET UTF8;
 
USE cloudDB02;
 
CREATE TABLE dept
(
  deptno BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
  dname VARCHAR(60),
  db_source   VARCHAR(60)
);
 
INSERT INTO dept(dname,db_source) VALUES('开发部',DATABASE());
INSERT INTO dept(dname,db_source) VALUES('人事部',DATABASE());
INSERT INTO dept(dname,db_source) VALUES('财务部',DATABASE());
INSERT INTO dept(dname,db_source) VALUES('市场部',DATABASE());
INSERT INTO dept(dname,db_source) VALUES('运维部',DATABASE());
 
SELECT * FROM dept;

需要注意的是,我们需要在yml配置文件中定义不同的实例名称,用来区分两个不同的服务提供者.

  instance:
    instance-id: helloword-cloud-provider(8002) #自定义服务名称
    prefer-ip-address: true #访问路径显示IP地址

但是他们对外暴露的服务名必须是一样的,也就是在application.yml中的:

spring:
  application:
    name: helloword-cloud-provider

上面这个属性,在两个provider中必须是一样的.代表两个不同的实例,提供相同的服务.

接着启动服务提供者8002,首先进行自测,访问url:http://localhost:8002/dept/list

可以看到第二个服务也可以正常调用了:

在这里插入图片描述

注意页面显示的数据所属的数据库,是我们新建的clouddb02,也就是和8001不同的库.

启动服务,我们可以看到Eureka Server的Web页面显示了这两个微服务的提供者:

在这里插入图片描述

好了,现在我们的服务提供者也有2个了,下面我们来对消费者进行改造,并且引入Ribbon进行负载均衡,访问我们的微服务.

一、Ribbon依赖的引入

首先要使用Ribbon,就要引入其依赖,这也是我们使用众多的开源组件的必须流程

helloword-cloud-consumer的POM文件中加入Ribbon的依赖:

        <!--Ribbon相关-->
        <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>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>

可以看到,Ribbon的使用是建立在config和eureka之上的.

二. 修改配置文件application.yml

其次,我们的服务消费者要消费服务,就要知道服务提供者的相关信息,所以需要去Eureka获取,因此要配置Eureka的地址

在application.yml文件中配置:

eureka:
  client:
    register-with-eureka: false #false表示不向注册中心注册自己
    service-url:
      defaultZone: http://localhost:7001/eureka/ #设置与eureka server交互的地址,如果有多个,可以用逗号隔开

由于我们的Ribbon是做负载均衡的,而我们这里是使用RestTemplate来进行RPC调用的,因此要在RestTemplate这边开启负载均衡.

    @Bean
    @LoadBalanced//开启负载均衡
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
三. 开启Eureka client功能

在这里,我们的服务消费者也是作为Eureka的客户端去请求Eureka服务端,获取服务列表的,所以要在主启动类中开启

@SpringBootApplication
@EnableEurekaClient
public class DeptConsumer80 {

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

}
四. 修改调用RPC的URL

在之前,我们通过restTemplate来调用,都是通过IP+端口号的方式.

而现在,我们在客户端引入Ribbon,通过负载均衡的方式从Eureka获取服务来调用,所以要更改URL:

//private static final String REST_URL_PREFIX = "http://localhost:8001";
    private static final String REST_URL_PREFIX = "http://HELLOWORD-CLOUD-PROVIDER";

这里直接更改为Eureka中的服务名,这样在调用的时候,Ribbon会按照某种负载均衡的方式,在指定的服务中寻找一个服务提供者来调用.

五. 启动服务,调用成功

现在我们启动消费者,在浏览器进行调用,就可以看到调用成功了:

在这里插入图片描述

刷新页面,可以看到服务调用成功了.这样我们就不用关心服务的地址和端口号,直接调用服务即可.这也是Ribbon和Eureka给我们带来的便利

3. Ribbon的负载均衡

在上面的步骤中,我们在配置类中加入了@LoadBalanced注解,这样在消费者进行请求时,会根据Ribbon的默认的负载均衡去调用微服务,我们多刷新几次,可以看到服务的调用时按照轮询的方式来调用的,除了轮询,Ribbon还提供了很多种的负载均衡策略,下面我们来改变Ribbon默认的轮询,改为随机的方式来访问微服务

  1. 首先我们创建一个负载均衡的配置类MyRule.java

    package com.xiaojian.myrule;
    
    import com.netflix.loadbalancer.IRule;
    import com.netflix.loadbalancer.RandomRule;
    import com.netflix.loadbalancer.RoundRobinRule;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class MyRule {
    
        @Bean
        public IRule initRule() {
            //return new RoundRobinRule();//轮询
            return new RandomRule();
        }
    }
    

    需要注意的是,这个配置类不能和主启动类放在同一个包下,这也是Ribbon官方的要求,因此可以看到上面这个类是在另外一个包里面的.

    在这个配置类中,我们给spring容器中注入了一个负载均衡算法,看名字我们就可以知道这个算法是随机算法.

  2. 下面我们要告诉spring,这个配置文件是Ribbon的配置文件,我们要使用这个配置文件指定的算法来访问微服务

    首先我们需要在消费者的主启动类上添加注解@RibbonClient(name = "HELLOWORD-CLOUD-PROVIDER", configuration = MyRule.class)

    注解属性解释:

    ​ name: 指定Ribbon要针对哪个服务做负载均衡

    ​ configuration: 指定Ribbon要使用的配置类

  3. 重启消费者,启动以后多调用几次,我们可以看到随机负载的配置生效了

以上方式,我们使用的是Ribbon默认的负载均衡算法,除了这些默认的算法,我们还可以自定义算法

需求: 现在有2个微服务提供者8001和8002,我们要求消费者在进行调用的时候,每个微服务调用5次以后再进行切换?

按照以上需求,我们还对消费者进行配置修改:

第一步: 实现自己的负载算法类

参考github上的RandomRule类,该类正是我们在容器中注入的负载算法对象类,我们要实现自己的负载算法,可以参考该类,修改对应的代码.

这里我们先看看官方的这个算法类:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.netflix.loadbalancer;

import com.netflix.client.config.IClientConfig;
import edu.umd.cs.findbugs.annotations.SuppressWarnings;
import java.util.List;
import java.util.Random;

public class RandomRule extends AbstractLoadBalancerRule {
    Random rand = new Random();

    public RandomRule() {
    }

    @SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        } else {
            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 = this.rand.nextInt(serverCount);
                //根据下标,从微服务中获取对应的服务
                server = (Server)upList.get(index);
                if (server == null) {
                    Thread.yield();
                } else {
                    if (server.isAlive()) {
                        //如果该服务存活,则返回
                        return server;
                    }

                    server = null;
                    Thread.yield();
                }
            }

            return server;
        }
    }

    public Server choose(Object key) {
        return this.choose(this.getLoadBalancer(), key);
    }

    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

该算法的主要方法都在choose()方法中,因此我们可以修改一下,创建我们自己的算法类:

package com.xiaojian.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;

public class MyLoadBalanceRule extends AbstractLoadBalancerRule
{

    // total = 0 // 当total==5以后,我们指针才能往下走,
    // index = 0 // 当前对外提供服务的服务器地址,
    // total需要重新置为零,但是已经达到过一个5次,我们的index = 1
    // 分析:我们5次,但是微服务只有8001 8002 因此在数组下标达到2的时候,重置回0
    //


    private int total = 0; 			// 总共被调用的次数,目前要求每台被调用5次
    private int currentIndex = 0;	// 当前提供服务的机器号

    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) {
                /*
                 * No servers. End regardless of pass, because subsequent passes only get more
                 * restrictive.
                 */
                return null;
            }

//			int index = rand.nextInt(serverCount);// java.util.Random().nextInt(3);
//			server = upList.get(index);


//			private int total = 0; 			// 总共被调用的次数,目前要求每台被调用5次
//			private int currentIndex = 0;	// 当前提供服务的机器号
            if(total < 5)
            {
                server = upList.get(currentIndex);
                total++;
            }else {
                total = 0;
                currentIndex++;
                if(currentIndex >= upList.size())
                {
                    currentIndex = 0;
                }
            }


            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);
            }

            // Shouldn't actually happen.. but must be transient or a bug.
            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

    }

}

第二部: 注入负载算法类

以上就是我们自己修改的负载算法类,接着我们在负载配置类中配置这个类:

	@Bean
    public IRule initRule() {
        //return new RoundRobinRule();//轮询
        //return new RandomRule();
        return new MyLoadBalanceRule();
    }

重启消费者服务,进行测试:

自行测试后可以看到,每个服务进行5次调用以后,就会调用下一个服务.

至此,我们的自定义负载算法实现了,我们可以根据我们的实际需求来定义负载算法,使我们的微服务发挥出更好的性能.

总结

至此,Ribbon的使用差不多也就到这里了,使用Ribbon可以时我们在调用微服务的时候,脱离IP和端口的限制,只要指定服务名就可以调用服务.

在下一章的博文中,我们将继续讲springcloud中的另一个组件----Feign,进一步学习SpringCloud技术栈.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值