引言
Nacos在Client选择节点时提供了一种基于权重的随机算法,通过源码分析掌握其实现原理,方便实战中加以运用。
一、内容提要
节点列表
假设注册了5个节点,每个节点的权重如下。
组织递增数组
目的在于形成weights数组,该数组元素取值[0~1]范围,元素逐个递增,计算过程如下图示。另外注意非健康节点或者权重小于等于0的不会被选择。
随机算法
通过生成[0~1]范围的随机数,通过二分法查找递增数组weights[]接近的index,再从注册节点列表中返回节点。
二、源码分析
随机权重负载均衡算法是在NacosNamingService#selectOneHealthyInstance提供,一起走查下。
- @Override
- publicInstanceselectOneHealthyInstance(StringserviceName,StringgroupName,booleansubscribe)
- throwsNacosException{
- returnselectOneHealthyInstance(serviceName,groupName,newArrayList<String>(),subscribe);
- }
- @Override
- publicInstanceselectOneHealthyInstance(StringserviceName,StringgroupName,List<String>clusters,
- booleansubscribe)throwsNacosException{
- StringclusterString=StringUtils.join(clusters,",");
- //注解@1
- if(subscribe){
- ServiceInfoserviceInfo=serviceInfoHolder.getServiceInfo(serviceName,groupName,clusterString);
- if(null==serviceInfo){
- serviceInfo=clientProxy.subscribe(serviceName,groupName,clusterString);
- }
- returnBalancer.RandomByWeight.selectHost(serviceInfo);
- }else{
- //注解@2
- ServiceInfoserviceInfo=clientProxy
- .queryInstancesOfService(serviceName,groupName,clusterString,0,false);
- returnBalancer.RandomByWeight.selectHost(serviceInfo);
- }
- }
注解@1 已订阅「从缓存获取注册节点列表」,默认subscribe为true。
注解@2 从 「从服务器获取注册节点列表」
- protectedstaticInstancegetHostByRandomWeight(List<Instance>hosts){
- NAMING_LOGGER.debug("entryrandomWithWeight");
- if(hosts==null||hosts.size()==0){
- NAMING_LOGGER.debug("hosts==null||hosts.size()==0");
- returnnull;
- }
- NAMING_LOGGER.debug("newChooser");
- List<Pair<Instance>>hostsWithWeight=newArrayList<Pair<Instance>>();
- for(Instancehost:hosts){
- if(host.isHealthy()){//注解@3
- hostsWithWeight.add(newPair<Instance>(host,host.getWeight()));
- }
- }
- NAMING_LOGGER.debug("for(Hosthost:hosts)");
- Chooser<String,Instance>vipChooser=newChooser<String,Instance>("www.taobao.com");
- //注解@4
- vipChooser.refresh(hostsWithWeight);
- NAMING_LOGGER.debug("vipChooser.refresh");
- //注解@5
- returnvipChooser.randomWithWeight();
- }
注解@3 非健康节点不会被选中,组装Pair的列表,包含健康节点的权重和Host信息
注解@4 刷新需要的数据,具体包括三部分:所有健康节点权重求和、计算每个健康节点权重占比、组织递增数组。
- publicvoidrefresh(){
- DoubleoriginWeightSum=(double)0;
- //注解@4.1
- for(Pair<T>item:itemsWithWeight){
- doubleweight=item.weight();
- //ignoreitemwhichweightiszero.seetest_randomWithWeight_weight0inChooserTest
- //weight小于等于0的将会剔除
- if(weight<=0){
- continue;
- }
- items.add(item.item());
- //值如果无穷大
- if(Double.isInfinite(weight)){
- weight=10000.0D;
- }
- //值如果为非数字值
- if(Double.isNaN(weight)){
- weight=1.0D;
- }
- //累加权重总和
- originWeightSum+=weight;
- }
- //注解@4.2
- double[]exactWeights=newdouble[items.size()];
- intindex=0;
- for(Pair<T>item:itemsWithWeight){
- doublesingleWeight=item.weight();
- //ignoreitemwhichweightiszero.seetest_randomWithWeight_weight0inChooserTest
- if(singleWeight<=0){
- continue;
- }
- //每个节点权重的占比
- exactWeights[index++]=singleWeight/originWeightSum;
- }
- //注解@4.3
- weights=newdouble[items.size()];
- doublerandomRange=0D;
- for(inti=0;i<index;i++){
- weights[i]=randomRange+exactWeights[i];
- randomRange+=exactWeights[i];
- }
- doubledoublePrecisionDelta=0.0001;
- if(index==0||(Math.abs(weights[index-1]-1)<doublePrecisionDelta)){
- return;
- }
- thrownewIllegalStateException(
- "CumulativeWeightcaculatewrong,thesumofprobabilitiesdoesnotequals1.");
- }
注解@4.1 所有健康节点权重求和originWeightSum
注解@4.2 计算每个健康节点权重占比exactWeights数组
注解@4.3 组织递增数组weights,每个元素值为数组前面元素之和
以一个例子来表示这个过程,假设有5个节点:
- 1.2.3.4100
- 1.2.3.5100
- 1.2.3.6100
- 1.2.3.780
- 1.2.3.860
步骤一 计算节点权重求和
- originWeightSum = 100 + 100 + 100 + 80 + 60 = 440
步骤二 计算每个节点权重占比
- exactWeights[0] = 0.2272
- exactWeights[1] = 0.2272
- exactWeights[2] = 0.2272
- exactWeights[3] = 0.1818
- exactWeights[4] = 0.1363
步骤三 组织递增数组weights
- weights[0] = 0.2272
- weights[1] = 0.4544
- weights[2] = 0.6816
- weights[3] = 0.8634
- weights[4] = 1
注解@5 随机选取一个,逻辑如下:
- publicTrandomWithWeight(){
- Ref<T>ref=this.ref;
- //注解@5.1
- doublerandom=ThreadLocalRandom.current().nextDouble(0,1);
- //注解@5.2
- intindex=Arrays.binarySearch(ref.weights,random);
- //注解@5.3
- if(index<0){
- index=-index-1;
- }else{
- //注解@5.4
- returnref.items.get(index);
- }
- //返回选中的元素
- if(index>=0&&index<ref.weights.length){
- if(random<ref.weights[index]){
- returnref.items.get(index);
- }
- }
- /*Thisshouldneverhappen,butitensureswewillreturnacorrect
- *objectincasethereissomefloatingpointinequalityproblem
- *wrtthecumulativeprobabilities.*/
- returnref.items.get(ref.items.size()-1);
- }
注解@5.1 产生0到1区间的随机数
注解@5.2 二分法查找数组中接近的值
注解@5.3 没有命中返回插入数组理想索引值
注解@5.4 命中直接返回选中节点
小结: 一种基于权重的随机算法的实现过程,扒开看也不复杂。作者小姐姐养的狗 。转载本文请联系小姐姐味道公众号。
原文链接:https://mp.weixin.qq.com/s/aq2Uymvv9EnnfgI8DJKd6Q





