RIbbon是什么?怎么实现的负载均衡?底层默认是如何实现的负载算法?带你深入其中

@

目录

负载方案简介:

目前主流的负载方案分为以下两种:
1.集中式负载均衡,在消费者和服务提供方中间使用独立的代理方式进行负载,有硬件的(比如 F5),也有软件的(比如 Nginx)。
2.客户端自己做负载均衡,根据自己的请求情况做负载,Ribbon 就属于客户端自己做负载。

而Spring Cloud Ribbon 虽然只是一个工具类框架,它不像服务注册中心、配置中心、API 网关那样需要独立部署,但是它几乎存在于每一个 Spring Cloud 构建的微服务和基础设施中。因为微服务间的调用,API 网关的请求转发等内容,实际上都是通过 Ribbon 来实现的

接下来就带大家通过几行源码的debug查看单机的Ribbon默认算法是实现的



1.工程中引入谷歌提供的guava工具类和Ribbon组件的依赖,

pom.xml:

  <!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>30.1.1-jre</version>
        </dependency>

        <dependency>
            <groupId>com.netflix.ribbon</groupId>
            <artifactId>ribbon</artifactId>
            <version>2.2.2</version>
        </dependency>
        <dependency>
            <groupId>com.netflix.ribbon</groupId>
            <artifactId>ribbon-core</artifactId>
            <version>2.2.2</version>
        </dependency>
        <dependency>
            <groupId>com.netflix.ribbon</groupId>
            <artifactId>ribbon-loadbalancer</artifactId>
            <version>2.2.2</version>
        </dependency>

2.测试类:

    public static void main(String[] args) {
        // 	注册两个server,顺序分别是8081,8082
        List<Server> serverList = Lists.newArrayList(
                new Server("localhost", 8081),
                new Server("localhost", 8082)
        );

        ILoadBalancer baseLoadBalancer = LoadBalancerBuilder.newBuilder().buildFixedServerListLoadBalancer(serverList);
        Server localhost = baseLoadBalancer.chooseServer("localhost");
        Server localhost2 = baseLoadBalancer.chooseServer("localhost");
        Server localhost3 = baseLoadBalancer.chooseServer("localhost");
        Server localhost4 = baseLoadBalancer.chooseServer("localhost");
        System.out.println(localhost);
        System.out.println(localhost2);
        System.out.println(localhost3);
        System.out.println(localhost4);
}

3.BoadBalancerBuilder.newBilder()源码逐行深入

// 这段代码是其核心,这里注册了默认的算法机制和choose的api
LoadBalancerBuilder.newBuilder().buildFixedServerListLoadBalancer(serverList);

接下来介绍我所认知的几个核心关键点

// 用来注册默认的负载均衡算法类,以配置类对象的形式初始化用到的属性
LoadBalancerBuilder.newBuilder()

具体流程:

3.1 执行newBilder()方法,进行创建"LoadBalancerBuilder"对象

RIbbon是什么?怎么实现的负载均衡?底层默认是如何实现的负载算法?带你深入其中

3.2 点进去,在创建“LoadBalancerBuilder”对象时,会初始化其类属性,这里有个关键点,在第16行会执行一个配置默认参数值的属性,并且返回其配置类

RIbbon是什么?怎么实现的负载均衡?底层默认是如何实现的负载算法?带你深入其中

3.3 此时会执行一个"default"默认的注册配置信息

RIbbon是什么?怎么实现的负载均衡?底层默认是如何实现的负载算法?带你深入其中
RIbbon是什么?怎么实现的负载均衡?底层默认是如何实现的负载算法?带你深入其中
RIbbon是什么?怎么实现的负载均衡?底层默认是如何实现的负载算法?带你深入其中

3.4 进入this.loadDefaultValues()方法

在这里会看到一大坨默认的注册信息,核心参数:"NFLoadBalancerRuleClassName" 请记住这个字段


咱们这里只看有关默认算法的代码,其他的不做扩展


public void loadDefaultValues() {
  xxxxxxxxxx        
  this.putDefaultStringProperty(CommonClientConfigKey.NFLoadBalancerRuleClassName, this.getDefaultNfloadbalancerRuleClassname());
  xxxxxxxxxx
}

RIbbon是什么?怎么实现的负载均衡?底层默认是如何实现的负载算法?带你深入其中

3.5 AvailabilityFilteringRule对象

他继承自ClientConfigEnabledRoundRobinRule对象 所以在后期创建的时候会执行其构造方法和类的属性。
RIbbon是什么?怎么实现的负载均衡?底层默认是如何实现的负载算法?带你深入其中

3.6 看到这里我们就知道了,RIbbon的默认算法是轮询的算法机制。

RIbbon是什么?怎么实现的负载均衡?底层默认是如何实现的负载算法?带你深入其中

4. buildFixedServerListLoadBalancer(servers)源码逐行深入

ILoadBalancer baseLoadBalancer =
                LoadBalancerBuilder
                        .newBuilder()
	                        .buildFixedServerListLoadBalancer(serverList);

4.1 点进去会看到 rule肯定是null,然后他会拿到刚刚解析的配置类进行从配置类信息中创建Rule的操作

RIbbon是什么?怎么实现的负载均衡?底层默认是如何实现的负载算法?带你深入其中

4.2 这里就看到我们在3.3中提到的”NFLoadBalancerRuleClassName“key拿到默认负载算法的Class对象。

在下面又通过ClientFactory.instantiateInstanceWithClientConfig(ruleClassName, config);方法进行className的实例化,拿到AvailabilityFilteringRule对象的实例,并且内部Rule默认为RoundRobinRule对象做负载均衡算法。
RIbbon是什么?怎么实现的负载均衡?底层默认是如何实现的负载算法?带你深入其中
除了RoundRobinRule以外还有其他的算法
RIbbon是什么?怎么实现的负载均衡?底层默认是如何实现的负载算法?带你深入其中

5.既然默认的算法机制搞清楚了 就可以进行choose server了

5.1 执行多个重载的choose方法

Server server = baseLoadBalancer.chooseServer();

往下直接看606行的choose方法
RIbbon是什么?怎么实现的负载均衡?底层默认是如何实现的负载算法?带你深入其中

继续看choose方法
RIbbon是什么?怎么实现的负载均衡?底层默认是如何实现的负载算法?带你深入其中
在点进去
RIbbon是什么?怎么实现的负载均衡?底层默认是如何实现的负载算法?带你深入其中

5.2 执行增量取模算法

这里核心的方法是44行的"this.incrementAndGetModulo(serverCount);"方法,它通过你传入的server列表个数拿到负载均衡后的索引值。
RIbbon是什么?怎么实现的负载均衡?底层默认是如何实现的负载算法?带你深入其中

算法实现是每次都拿这个AtomicInteger类型值做当前server数组的下标,每次choose都get出当前下标,进行+1再取模 达到雨露均沾的负载均衡效果,并且使用cas方式操作值的变化。

private AtomicInteger nextServerCyclicCounter;

RIbbon是什么?怎么实现的负载均衡?底层默认是如何实现的负载算法?带你深入其中

5.3 算法执行完后进行检查处理

取出server后会进行server的检查判断:
1.如果server是null或者isAlive为false,则continue这次循环,继续下一次轮询
2.如果轮询次数超过10次,则会输入wran警告,并且返回null
log.warn("No available alive servers after 10 tries from load balancer: " + lb);



6.默认算法需要注意点

RIbbon是什么?怎么实现的负载均衡?底层默认是如何实现的负载算法?带你深入其中

这里有意思的是,它内部算法先进行+1,然后就是1+1再%长度的运算。所以第一次取出来的就是server服务列表中的第二个server了...
存的顺序是8081,8082 默认取出来顺序是8082 8081 8082 8081



写到这里就结束了,基本是对默认的负载算法有一个流程的认识。如果不太了解Ribbon单机试玩版如何操作的话 看完可以帮助大家~

7.使用RxJava构建Ribbon

在结尾给大家附上一个通过RxJava实现的代码:

pom.xml:

<dependency>
    <groupId>io.reactivex</groupId>
    <artifactId>rxjava</artifactId>
    <version>1.0.10</version>
</dependency>

Java:

// 服务列表
List<Server> serverList = Lists.newArrayList(new Server("localhost", 8081), new Server("localhost", 8083));
// 构建负载实例
ILoadBalancer loadBalancer = LoadBalancerBuilder.newBuilder().buildFixedServerListLoadBalancer(serverList);
// 调用 5 次来测试效果
for (int i = 0; i < 5; i++) {
    String result = LoadBalancerCommand.<String>builder().withLoadBalancer(loadBalancer).build()
            .submit(new ServerOperation<String>() {
                public Observable<String> call(Server server) {
                    try {
                        String addr = "http://" + server.getHost() + ":" + server.getPort() + "/user/hello";
                        System.out.println(" 调用地址:" + addr);
                        URL url = new URL(addr);
                        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                        conn.setRequestMethod("GET");
                        conn.connect();
                        InputStream in = conn.getInputStream();
                        byte[] data = new byte[in.available()];
                        in.read(data);
                        return Observable.just(new String(data));
                    } catch (Exception e) {
                        return Observable.error(e);
                    }
                }
            }).toBlocking().first();
    System.out.println(" 调用结果:" + result);
}
上一篇:spring-cloud服务之间的调用之ribbon


下一篇:Ribbon的负载均衡策略及原理