Springcloud学习之路三(Ribbon负载均衡)

783 阅读11分钟

上篇博文Eureka服务注册与发现实例:juejin.cn/post/684490…

通过上篇博客,我们手动的搭建了微服务项目,并使用端口号80作为消费者,端口号为7001、7002作为服务注册中心,端口号为8080做了生产者,并使用Eureka提供服务注册与发现,接下来我们使用Ribbon负载均衡来实现消费者80去服务发现中心获取服务列表并通过轮询算法去调用单独的服务提供者。。。

1、Ribbon是什么?

  • 1、Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡工具
  • 2、Spring Cloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。因为微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的,包括后续我们将要介绍的Feign,它也是基于Ribbon实现的工具。所以,对Spring Cloud Ribbon的理解和使用,对于我们使用Spring Cloud来构建微服务非常重要
  • 3、Ribbon是NetFlix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将NetFlix的中间层服务连接再一起,Ribbon的客户端组件提供一系列完整的配置项,如:连接超时,重试等,简单来说,在配置文件中列出LoadBlancer(简称LB:负载均衡)后面所有的机器,Ribbon会自动的帮助你基于某种规则(简单轮询、随机连接等)去连接这些机器,我们很容易使用Ribbon实现自定义的负载均衡算法。

2、Ribbon怎么玩?

  • 1、负载均衡介绍

LB,负载均衡,在微服务或分布式集群中经常使用的一种应用

负载均衡(LB):简单来说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)

常见的负载均衡软件:NginxlvsApache+Tomcat 负载均衡分类:

集中式LB:

  • 在服务的消费方和提供方之间使用独立的LB设施,如Nginx:反向代理!,由该设施负责把访问请求通过某种策略转发至服务的提供方

进程式LB:

  • 把LB逻辑集成到消费方,消费方服务注册中心获知哪些地址可用,然后自己再从这些地址中选出一个合适的服务器
  • Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。**
  • 2、Ribbon怎么在微服务项目中使用

通过Spring Cloud Ribbon的封装,我们在微服务架构中使用客户端负载均衡调用非常简单,只需要如下两步:

▪️服务提供者只需要启动多个服务实例并注册到一个注册中心或是多个相关联的服务注册中心。

▪️服务消费者直接通过调用被@LoadBalanced注解修饰过的RestTemplate来实现面向服务的接口调用。

这样,我们就可以将服务提供者的高可用以及服务消费者的负载均衡调用一起实现了。


详细配置如下:

第一步:倒依赖

首先我们在微服务项目中的消费者(端口为80)中引入Ribbon和Eureka依赖(Ribbon作用是提供负载均衡,让消费者从注册中心获取到服务列表的地址,Eureka作用:方便消费者在注册中心发现更多服务)

 <!--给消费者引入Ribbon依赖。让消费者从注册中心获取到服务列表的地址-->
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-ribbon -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
        <!--给消费者加入Eureka客户端,方便消费者在注册中心发现更多服务-->
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.4.3.RELEASE</version>
        </dependency>

第二步:写配置

编写eureka的配置,让消费者可以通过多个注册中心节点发现更多的服务。

eureka:
  client:
    register-with-eureka: false  #表示不向eureka注册自己
    service-url:  # 表示消费者可以从注册中心发现这两个服务节点
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

第三步:修改服务消费者的Config文件

RestTemplate对象会使用Ribbon的自动化配置,同时通过配置@LoadBalanced还能够开启客户端负载均衡

 @LoadBalanced //Ribbon实现负载均衡的注解

第四步:修改之前消费者的Controller。

让消费者访问的地址变为一个变量,通过服务名来访问,这个服务名存在多个注册中心里,通过Ribbon负载均衡机制告诉消费者获得的是哪个服务器,消费者去远程调用哪个生产者服务器进行消费。

    //用ribbon实现负载均衡时,我们消费者访问的地址就是一个变量,通过服务名来访问,
    // 这个服务名存在Eureka的多个注册中心里,通过Ribbon负载均衡机制获取它到底调用哪个生产者
    private static final String REST_URL_PREFIX = "http://springcloud-provider";

第五步:消费者配置启动类中加入euureka客户端注解

@EnableEurekaClient  //eureka客户端注解  使用的是eureka发现功能

第六步:测试(由于本人电脑太垃圾,没有跑起来四个服务,所以测试自己测哦!)

开启EurekaServer两个服务器(7001、7002),开启Eureka客户端服务器(8080),开启消费者服务器(80)、浏览器通过接口参数localhost/consumer/dept/list直接获取数据,不用关系IP和端口号

3、带你看懂Ribbon的负载均衡

图中具体流程如下:(图中实线部分为Ribbon用途:1、查询可用服务列表 2、通过负载均衡算法获取一个服务地址去远程连接)

现在有三个服务提供者同时向Eureka集群中注册服务,服务消费者在Eurefa集群中进行服务发现,然后Ribbon会获取集群中可用的服务列表地址返回给服务消费者,服务消费者可以通过负载均衡机制从服务列表中获取一个服务地址去远程调用服务提供者进行消费服务。

  • 1、首先我们创建三个单独的数据库
  • 2、然后创建三个服务提供者(端口号不同,配置文件中连接各自的数据库,但是spring.application.name服务名必须一样,代表同一个服务,启动类不一样)

  • 3、测试(打开三个服务注册集群Eureka(7001、7002、7003)、打开三个服务提供者(8001、8002、8003)、打开服务消费者,在浏览器地址栏中输入localhost/consume/dept/list,即可出现如图效果)

注意:每发送一次请求,访问到的服务提供者都不一样,这是Ribbon通过负载均衡轮询算法根据返回的服务提供者列表选择一个适合的服务提供者去远程调用连接,Rebbin默认负载均衡采用的是轮询算法

4、自定义实现Ribbon负载均衡策略算法

  • 1、带你看源码

我们在使用Ribbon负载均衡时,通过@LoadBalanced注解开启负载均衡

但是Ribbon负载均衡内部实现是靠Irule接口来实现的。接下来我们看看Irole接口,这个接口里有三个方法

接下来我们看看所有策略算法是怎么实现负载均衡的呢???

看随机策略算法的源码:

所有策略算法都是extends AbstractLoadBalancerRule,AbstractLoadBalancerRuleimplements Irole,从这个算法我们可以看出,里面有个重要的方法,就是如何选择服务,实现原理是:1、先判断负载均衡中是否为null,如果不为null,我们定义server = null,然后判断线程是否被中断,如果不中断,先获取所有存活的服务,再获取所有的服务,然后根据随机算法获取服务的下标,根据下标从存活服务获取一个服务,最后判断服务是否为null,为空让线程yield,否则服务存活,返回服务。

//
// 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.concurrent.ThreadLocalRandom;

public class RandomRule extends AbstractLoadBalancerRule {
    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.chooseRandomInt(serverCount);
                server = (Server)upList.get(index);
                if (server == null) {
                    Thread.yield();
                } else {
                    if (server.isAlive()) {
                        return server;
                    }

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

            return server;
        }
    }

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

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

    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

Ribbon负载均衡策略算法有很多,接下来我们举几个例子来说明下他们的用途

  • 2、我们来自定义实现负载均衡策略算法

我们先来看看SpringCloud官网中Ribbon是怎么实现自定义算法的www.springcloud.cc/spring-clou…


注意:

从官方文档中我们可以看到要使用自定义Ribbon负载均衡算法时,必须不能把自己的配置类和springboot启动类放在同一个目录或者启动类的子目录下,如果放在了同目录下,自己的配置类就不能覆盖Ribbon默认的算法,springboot默认就会扫描这个自定义的配置类,为了不让扫描到自定义配置类,所以放在不同包下。


包结构如下:

第一步:编写自定义复杂均衡策略算法

此算法思想:修改源码中随机策略算法,将随机生成改成每个服务调用5次,完成之后换成下一个服务,所有服务访问完,又从第一个服务开始访问

package com.baoji.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.concurrent.ThreadLocalRandom;

public class SelfRandomRole extends AbstractLoadBalancerRule {

    //自定义策略算法
    //每个服务访问5次,换下一个服务(服务总共3个)
    //total = 0  默认total=0,当total=5,指向下一个服务
    //currentIndex = 0  默认=0,如果total=5,currentIndex+1
    private int total = 0;  //被调用的次数
    private int currentIndex = 0;  //当前谁在调用服务

    // @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.chooseRandomInt(serverCount);  //随机选择一个服务的随机数下标
               // server = (Server)upList.get(index); //根据随机的下标获取存活的服务
                //-===================================================================
                //自定义策略算法
                if(total<5){
                    server = upList.get(currentIndex);//获取存活的服务
                    total++;  //被调用的次数+1
                }else{
                    total = 0;
                    currentIndex++;
                    if(currentIndex>upList.size()){ //如果当前服务下标>存活服务的数量
                        currentIndex = 0;
                    }
                    server = upList.get(currentIndex);//获取存活的服务
                }


                //-===================================================================

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

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

            return server;
        }
    }

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

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

    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

第二步:将我们编写的负载均衡算法手动注入到该项目中,使用@Bean注解

package com.baoji.myrule;

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

@Configuration
public class SelfRole {
    //此处不使用Ribbon默认的负载均衡算法,使用随机算法
    @Bean
    public IRule myRule(){
        return new SelfRandomRole(); //默认是轮询,现在采用的是我们自己的策略算法
    }
}

第三步:在启动类上加入@RibbonClient(name="springcloud-provider",configuration = SelfRole.class)注解,目的是在微服务启动的时候就能去加载我们自定义的Ribbon类

自定义实现Ribbon的负载均衡策略算法是覆盖默认的Ribbon负载均衡轮询算法

//在微服务启动的时候就能去加载我们自定义的Ribbon类(注意:自定义的配置类不能和启动类目录同级,不然就会被扫描到,这样我们编写自定义的负载均衡算法就不能覆盖默认的轮询算法)
@RibbonClient(name="springcloud-provider",configuration = SelfRole.class)

第四步:测试消费者

此时我们请求查询服务时,我们就可以按照自定义实现的负载均衡算法来实现每个服务请求5次之后接着访问下一次服务。


今天我们学到了Ribbon浏览器负载均衡在微服务项目中是如何配置和使用、怎样自定义实现Ribbon负载均衡策略算法,接下来我们会学习Feign(使用接口方式调用服务),期待下次的博客内容吧!!!

推荐阅读Ribbon系列文章: www.jianshu.com/p/1bd66db5d…(此篇博文对于RestTemplate对象远程调用的具体底层实现讲的很清楚,希望读者可以深入底层)