首页技术文章正文

ribbon的常用负载均衡算法分析

更新时间:2021年03月26日 13时44分35秒 来源:黑马程序员

1577370495235_学IT就到黑马程序员.gif



1.Ribbon介绍

因为微服务是目前互联网公司比较流行的架构,所以spring就提供了一个顶级框架-spring cloud,来解决我们在开发微服务架构中遇到的各种各样的问题,今天的主角是spring cloud 框架中集成的组件Ribbon,那么Ribbon能解决什么问题呢,我们来思考下面的问题。

微服务架构中的每个服务为了高可用,很大程度上都会进行集群,我们假设现在集群了3个user服务,同时能提供相同的服务,问题来了,我们如何决定调用这3个user服务中的哪一个呢?

根据不同分析角度,会有不同的答案,也可以理解为根据不同的情况,我们可以写不同的算法,来决定到底此时此刻,调用这3个user服务的哪一个,那么,Ribbon就给我们提供了不同的算法,我们可以根据业务场景,调整配置文件,决定到底使用哪个算法,这样,算法中就会计算出调用哪个user服务了。

2.准备工作

1)我们准备一个eureka注册中心

2)再准备一个order服务

3)再准备3个相同代码的user服务,这样,order服务通过eureka注册中心,就可以发现user的3个服务

3.Ribbon的常用负载均衡策略

Ribbon是通过IRule的这个接口来选择3个user服务中的哪个的,但是实际执行的代码肯定是继承了这个接口的实现类,所以选择不同的实现类,就会选择不同负载均衡策略

public interface IRule {
    
    Server choose(Object var1);

    void setLoadBalancer(ILoadBalancer var1);

    ILoadBalancer getLoadBalancer();
}

3.1. RoundRobinRule  轮询策略

此策略是Ribbon的默认策略,是按照顺序,依次对所有的user服务进行访问。

通过重写IRule的choose方法,来选择并返回决定调用的user服务,在下面的源码中,List allServers = lb.getAllServers(); 获得了所有的3个user服务实例,int nextServerIndex = this.incrementAndGetModulo(serverCount); 保存了当前调用的user实例的序号,然后就可以按照顺序调用下一个user服务了

public Server choose(ILoadBalancer lb, Object key) {
    if (lb == null) {
        log.warn("no load balancer");
        return null;
    } else {
        Server server = null;
        int count = 0;

        while(true) {
            if (server == null && count++ < 10) {
                List<Server> reachableServers = lb.getReachableServers();
                List<Server> allServers = lb.getAllServers();
                int upCount = reachableServers.size();
                //总服务实例数量
                int serverCount = allServers.size();
                if (upCount != 0 && serverCount != 0) {
                    int nextServerIndex = this.incrementAndGetModulo(serverCount);
                    server = (Server)allServers.get(nextServerIndex);
                    if (server == null) {
                        Thread.yield();
                    } else {
                        if (server.isAlive() && server.isReadyToServe()) {
                            return server;
                        }

                        server = null;
                    }
                    continue;
                }

                log.warn("No up servers available from load balancer: " + lb);
                return null;
            }

            if (count >= 10) {
                log.warn("No available alive servers after 10 tries from load balancer: " + lb);
            }

            return server;
        }
    }
}

debug的图例:

第一次访问:访问的是第一个实例

1616729974058_01.png

第二次访问:访问的是第二个实例

1616730000374_02.png

3.2. RoundRobinRule  随机策略

就和这个策略的名字一样,是对user的3个服务的随机调用,所以不存在规律,如下源码中int index = this.chooseRandomInt(serverCount); 通过随机数来选择下标,所以对user服务的调用是随机的

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

debug的图例:

第一次访问:访问的是第一个实例

1616730026639_03.png

第二次访问:访问的还是第一个实例

1616730049712_04.png

第三次访问:访问的是第三个实例

undefined

3.3. WeightedResponseTimeRule响应时间加权重策略

根据user的3个服务的响应时间来分配权重,响应时间越长的服务,权重越低,那么被调用的概率也就越低。相反,响应时间越短的服务,权重越高,被调用的概率也就越高

响应时间加权重策略的实现分为两步:

  1. WeightedResponseTimeRule实现类中默认情况下每隔30秒会统计一次每个服务的权重,在此30秒内,用的是轮询策略
  2. 30秒之后,会根据统计的结果来分配每个实例的权重,然后根据权重来分配调用次数
extends RoundRobinRule
public Server choose(ILoadBalancer lb, Object key) {
    if (lb == null) {
        return null;
    } else {
        Server server = null;

        while(server == null) {
            List<Double> currentWeights = this.accumulatedWeights;
            if (Thread.interrupted()) {
                return null;
            }

            List<Server> allList = lb.getAllServers();
            int serverCount = allList.size();
            if (serverCount == 0) {
                return null;
            }

            int serverIndex = 0;
            double maxTotalWeight = currentWeights.size() == 0 ? 0.0D : (Double)currentWeights.get(currentWeights.size() - 1);
            //在30秒之内,maxTotalWeight变量会一直是0.0
            if (maxTotalWeight >= 0.001D && serverCount == currentWeights.size()) {
                double randomWeight = this.random.nextDouble() * maxTotalWeight;
                int n = 0;

                for(Iterator var13 = currentWeights.iterator(); var13.hasNext(); ++n) {
                    Double d = (Double)var13.next();
                    if (d >= randomWeight) {
                        serverIndex = n;
                        break;
                    }
                }

                server = (Server)allList.get(serverIndex);
            } else {
                server = super.choose(this.getLoadBalancer(), key);
                if (server == null) {
                    return server;
                }
            }

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

                server = null;
            }
        }

        return server;
    }
}

debug的图例:

前几次访问:maxTotalWeight都是0.0,使用轮询策略,但是开始缓存权重数据

1616730100101_06.png

30秒之后:开始根据权重数据来分配权重,选择实例

1616730118844_07.png

如下图:8081端口的权重显然没有8082的权重大,所以8082端口的user服务实例被访问的次数多

1616730136639_08.png

1616730143541_09.png

3.4. RetryRule 重试策略

重试策略是指通过轮询策略选出一个实例,然后去访问,如果此实例为null或者已经失效,那么会重试其他的实例,answer = this.subRule.choose(key); 会根据轮询策略选择一个实例,然后if ((answer == null || !answer.isAlive()) && System.currentTimeMillis() < deadline)判断如果实例为null或者失效,那么会重新选择

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


1616730163380_10.png

3.5. BestAvailableRule 最低并发策略

会根据每个服务实例的并发数量来决定,访问并发数最少的那个服务,int concurrentConnections = serverStats.getActiveRequestsCount(currentTime); 会获得当前遍历的实例的并发数,然后和其他的实例的并发数进行判断,最终访问并发量最少的那个实例

public Server choose(Object key) {
    if (this.loadBalancerStats == null) {
        return super.choose(key);
    } else {
        List<Server> serverList = this.getLoadBalancer().getAllServers();
        int minimalConcurrentConnections = 2147483647;
        long currentTime = System.currentTimeMillis();
        Server chosen = null;
        Iterator var7 = serverList.iterator();

        while(var7.hasNext()) { //遍历所有的实例
            Server server = (Server)var7.next();
            ServerStats serverStats = this.loadBalancerStats.getSingleServerStat(server);
            if (!serverStats.isCircuitBreakerTripped(currentTime)) {
                int concurrentConnections = serverStats.getActiveRequestsCount(currentTime); //判断并发数,并和已经判断出的最少的并发数比较
                if (concurrentConnections < minimalConcurrentConnections) {
                    minimalConcurrentConnections = concurrentConnections;
                    chosen = server;
                }
            }
        }

        if (chosen == null) {
            return super.choose(key);
        } else {
            return chosen;
        }
    }
}

3.6. AvailabilityFilteringRule 可用过滤策略

此策略会聪明的过滤掉一直失败并被标记为circuit tripped的user服务,而且会过滤掉那些高并发的user服务

public Server choose(Object key) {
    int count = 0;
    
    for(Server server = this.roundRobinRule.choose(key); count++ <= 10; server = this.roundRobinRule.choose(key)) {
        //通过predicate来过滤
        if (this.predicate.apply(new PredicateKey(server))) {
            return server;
        }
    }
    //过滤掉一些服务之后,会采用轮询的方式调用剩下的服务
    return super.choose(key);
}

3.7. ClientConfigEnabledRoundRobinRule 自定义策略

此策略本身并没有实现什么特殊的处理逻辑,但是可以通过重置LoadBalancer来达到自定义一些高级策略的目的,可以重写initWithNiwsConfig和setLoadBalancer

public void initWithNiwsConfig(IClientConfig clientConfig) {
    this.roundRobinRule = new RoundRobinRule();
}

public void setLoadBalancer(ILoadBalancer lb) {
    super.setLoadBalancer(lb);
    this.roundRobinRule.setLoadBalancer(lb);
}

public Server choose(Object key) {
    if (this.roundRobinRule != null) {
        return this.roundRobinRule.choose(key);
    } else {
        throw new IllegalArgumentException("This class has not been initialized with the RoundRobinRule class");
    }
}


猜你喜欢:

区块链是什么?如何理解区块链?

IO和NIO有什么区别?NIO有什么优点?

Ribbon负载均衡算法父接口IRule接口介绍

FastDFS是怎么执行的?

黑马程序员高级java软件开发工程师课程

分享到:
在线咨询 我要报名
和我们在线交谈!