说说nacos客户端的随机权重算法

说说nacos客户端的随机权重算法

nacos简介

作为Spring Cloud Alibaba的核心组件,nacos完美替代了Spring Cloud中的注册中心eureka和配置中心Spring Cloud config。
nacos集服务发现与配置管理为一体,不仅是更加优秀的UI、更方便的可视化操作,还有在支持AP的同时,可通过指令切换成CP模式,使得不少使用Zookeeper的项目也加入到了nacos的大家庭。

随机权重算法

nacos和eureka相同的是都借用了ribbon作为客户端负载均衡工具,区别在于eureka的客户端支持ribbon的7种基础负载均衡策略和自定义的负载均衡策略,而nacos只支持已经封装好的随机权重负载策略。以下是基于nacos1.4.0源码的分析

com\alibaba\nacos\client\naming\core\Balancer.class

/**
     * Return one host from the host list by random-weight.
     *
     * @param hosts The list of the host.
     * @return The random-weight result of the host
     */
    protected static Instance getHostByRandomWeight(List<Instance> hosts) {
    
    
    	// 对入参服务列表的校验
        if (hosts == null || hosts.size() == 0) {
    
    
            return null;
        }
        List<Pair<Instance>> hostsWithWeight = new ArrayList<Pair<Instance>>();
        // 获取服务列表中健康状态的节点信息
        for (Instance host : hosts) {
    
    
            if (host.isHealthy()) {
    
    
                hostsWithWeight.add(new Pair<Instance>(host, host.getWeight()));
            }
        }
        // 以下是核心算法的实现
        Chooser<String, Instance> vipChooser = new Chooser<String, Instance>("www.taobao.com");
        vipChooser.refresh(hostsWithWeight);
        return vipChooser.randomWithWeight();
    }

com\alibaba\nacos\client\naming\utils\Chooser.class

 public void refresh() {
    
    
            Double originWeightSum = (double) 0;
            // 第一个循环,对服务信息中的权重进行校验,考虑的十分细致,包括负值、无穷大值、null值
            // 并计算出总权重的大小
            for (Pair<T> item : itemsWithWeight) {
    
    
                double weight = item.weight();
                if (weight <= 0) {
    
    
                    continue;
                }
                // 统计权重大于0的节点信息
                items.add(item.item());
                if (Double.isInfinite(weight)) {
    
    
                    weight = 10000.0D;
                }
                if (Double.isNaN(weight)) {
    
    
                    weight = 1.0D;
                }
                originWeightSum += weight;
            }
            
            double[] exactWeights = new double[items.size()];
            int index = 0;
            // 第二个循环,计算每个节点的权重大小
            for (Pair<T> item : itemsWithWeight) {
    
    
                double singleWeight = item.weight();
                // 二次校验权重是否小于等于0
                if (singleWeight <= 0) {
    
    
                    continue;
                }
                exactWeights[index++] = singleWeight / originWeightSum;
            }
            
            weights = new double[items.size()];
            double randomRange = 0D;
            // 第三个循环,计算权重的范围,统计总权重
            for (int i = 0; i < index; i++) {
    
    
                weights[i] = randomRange + exactWeights[i];
                randomRange += exactWeights[i];
            }
            
            // 由于double计算存在精度丢失,该变量是允许存在的误差
            double doublePrecisionDelta = 0.0001;
            if (index == 0 || (Math.abs(weights[index - 1] - 1) < doublePrecisionDelta)) {
    
    
                return;
            }
            throw new IllegalStateException(
                    "Cumulative Weight caculate wrong , the sum of probabilities does not equals 1.");
        }

图解

在这里插入图片描述

    /**
     * Random get one item with weight.
     *
     * @return item
     */
    public T randomWithWeight() {
    
    
        Ref<T> ref = this.ref;
        double random = ThreadLocalRandom.current().nextDouble(0, 1);
        // 用二分法查找生成的随机数在哪个权重范围,如果不存在,则返回-(插入点+1)
        int index = Arrays.binarySearch(ref.weights, random);
        if (index < 0) {
    
    
        	// 不存在,返回插入点右边的第一个索引
            index = -index - 1;
        } else {
    
    
        	// 存在,直接返回索引对应的值
            return ref.items.get(index);
        }
        
        if (index >= 0 && index < ref.weights.length) {
    
    
        	// index为插入点右边的第一个索引,所以random < ref.weights[index]恒成立
            if (random < ref.weights[index]) {
    
    
                return ref.items.get(index);
            }
        }
        
        /* This should never happen, but it ensures we will return a correct
         * object in case there is some floating point inequality problem
         * wrt the cumulative probabilities. */
        return ref.items.get(ref.items.size() - 1);
    }

总结

最近看了nacos和XXL的源码,对比来说,XXL的源码更接地气一些,nacos的源码封装程度更高,相关的算法十分精妙,特别是对于入参的校验以及各种场景的思考。最后说一下权重随机算法在实战场景中比较鸡肋的地方。首先,权重默认是1,可以通过设置参数来进行修改,但相同的服务一般是使用同样的参数进行启动的,虽然nacos的管理台可以对指定的服务节点的权重进行修改,但不可能每次发版都去做相应的修改。最后是思考,为什么不在启动时,就对非法的权重参数进行报错,而是到了服务发现的时候才去做参数的处理。1、如果是在启动时就对非法的权重参数通过抛异常来做预警,使得项目和中间件耦合度过高,随着微服务引入的中间件越来越多,能让服务无感知的中间件才是好中间件。2、服务节点的信息是存储在数据库,存在直接修改数据库的可能,所以这边做了两层校验,即前端保存时的检验和进行服务发现时的校验。

猜你喜欢

转载自blog.csdn.net/weixin_43776741/article/details/110229264