Ribbon几种负载均衡策略代码解析

我们都知道SpringCloud中的本地负载均衡组件Ribbon,Feign中集成了Ribbon和Hystrix,使得我们使用Ribbon变得更加方便。

Ribbon中的负载均衡策略都继承自AbstractLoadBalancerRule这个抽象类。

AbstractLoadBalancerRule 实现了 IRule 和 IClientConfigAware 这两个接口。

1) IClientConfigAware这个很好理解,看它的方法就知道,用于加载配置信息的。

2) IRule中的choose()方法是核心方法,具体负载均衡策略核心逻辑就在这个方法中,它返回的是是一个Server,代表请求将要调用的服务。IRule还将ILoadBanlancer这个类复合进我们的负载均衡策略类中,因为到时候需要使用ILoadBanlancer中的获取服务列表等操作。

一、RandomRule

最简单的策略,从代码来看分为以下步骤:

1. Random rand = new Random(); //使用java自带的随机数生成类

2. List<Server> upList = lb.getReachableServers();// 可用服务列表

    List<Server> allList = lb.getAllServers();//所有服务列表

3. in serverCount = allList.size() 

2. int index = this.rand.nextInt(serverCount); //随机数范围是0到serverCount(不包括serverCount)

3. server = (Server)upList.get(index); //上面范围是以所有服务的列表大小算,但是取值却从upList取,为啥,懂的评论区留言提点下我

部分代码截图:

二、RoundRobinRule

轮询是Ribbon默认的负载均衡策略,逻辑也比较简单,定义一个AtomicInteger初始值为0(不断调用choose时,这个值是不停增长的)每次用它加1后对serverCount取余,获取到的int值用来从allServers列表中get一个server,同时使用CAS使AtomicInteger的值加1。

获取到的server需要判断是否可用,可用则返回,不可用继续AtomicInteger取余那个过程。

以上轮询最多只会重试10次(通过while循环),如果还获取不到可用server则打印warn日志,直接返回null

看下主要步骤的代码:

int nextServerIndex = this.incrementAndGetModulo(serverCount);
server = (Server)allServers.get(nextServerIndex);

这个是取余方法:

private int incrementAndGetModulo(int modulo) {
        int current;
        int next;
        do {
            current = this.nextServerCyclicCounter.get(); // 这就是刚才我说的AtomicInteger变量,这是一个类成员变量
            next = (current + 1) % modulo;
        } while(!this.nextServerCyclicCounter.compareAndSet(current, next));

        return next;
}

 三、RetryRule

首先我们从开头就可以看到,RetryRule是基于RoundRobinRule的:

接下来分析他的choose方法,看看他和普通轮询有啥不同:

public Server choose(ILoadBalancer lb, Object key) {
        long requestTime = System.currentTimeMillis();
        long deadline = requestTime + this.maxRetryMillis;
        Server answer = null;
        answer = this.subRule.choose(key);
        if ((answer == null || !answer.isAlive()) && System.currentTimeMillis() < deadline) {
            InterruptTask task = new InterruptTask(deadline - System.currentTimeMillis());

            while(!Thread.interrupted()) {
                answer = this.subRule.choose(key);
                if (answer != null && answer.isAlive() || System.currentTimeMillis() >= deadline) {
                    break;
                }

                Thread.yield();
            }

            task.cancel();
        }

        return answer != null && answer.isAlive() ? answer : null;
    }

第一步:首先它利用请求调用的当前时间,加上最大允许重试时间,得到了一个终止时间。

第二步:然后调用轮询策略里的choose

第三步:返回的服务不可用并且还没超出终止时间,则进入下一步重试逻辑

第四步:构造一个定时任务InterruptTask,这个InterruptTask会在到终止时间的时候,将当前线程interupt

第五步:只要当前线程未被中断,重新调用轮询的choose,这里得到有用的server的话,就直接跳出循环,然后返回server了

第六步:第五步如果得到的server不可用,那么将当前线程暂停(转换成可执行状态)

总结:

RoundRobinRule如果轮询获取到的server不可用,最多会重试9次,直到返回可用server;

RetryRule如果轮询获取到的server不可用,只会重试一次,还不行就会将当前线程暂停,当然有可能刚暂停的线程立马又被调度;

posted @ 2022-09-07 21:18  方山客  阅读(210)  评论(0编辑  收藏  举报