之前我们讲了Nacos客户端如何获取实例列表,如何进行缓存处理,如何订阅实例列表的变化。在获取到一个实例列表后,你有没有想过一个问题:如果实例列表中有100个实例,Nacos客户端如何选择其中的一个?本文将带大家从源码层面进行分析。Nacos客户端采用如何使用算法从实例列表中获取一个实例来请求。也可以称为Nacos客户端的负载均衡算法。获取单个实例NamingService不仅提供了获取实例列表的方法,还提供了获取单个实例的方法,如:InstanceselectOneHealthyInstance(StringserviceName,StringgroupName,Listclusters,booleansubscribe)throwsNacosException;该方法将根据预定义的负载算法,从实例列表中获取一个健康的实例。其他重载的方法有类似的功能,最终都会调用这个方法。下面以该方法为例,分析一下具体的算法。具体实现代码:@OverridepublicInstanceselectOneHealthyInstance(StringserviceName,StringgroupName,Listclusters,booleansubscribe)throwsNacosException{StringclusterString=StringUtils.join(clu??sters,",");if(subscribe){//获取ServiceInfoServiceInfoserviceInfo=serviceInfoHolder.getServiceInfo(serviceName,groupName,clusterString);if(null==serviceInfo){serviceInfo=clientProxy.subscribe(serviceName,groupName,clusterString);}//通过负载均衡算法获取其中一个实例returnBalancer.RandomByWeight.selectHost(serviceInfo);}else{//GetServiceInfoServiceInfoserviceInfo=clientProxy.queryInstancesOfService(serviceName,groupName,clusterString,0,false);//通过负载均衡算法获取其中一个实例returnBalancer.RandomByWeight.selectHost(serviceInfo);}}selectOneHealthyInstance方法的逻辑很简单,调用我们之前说的获取方法获取ServiceInfo对象,然后传递给负载均衡算法作为参数,由负载均衡算法计算最终使用哪个实例(Instance)。算法参数封装先追溯代码实现,非核心业务逻辑,简单提一下。从上面的代码可以看出调用了Balancer内部类RandomByWeight的selectHost方法:publicstaticInstanceselectHost(ServiceInfodom){//从ServiceInfoListhosts=selectAll(dom)中获取实例列表;//...returngetHostByRandomWeight(hosts);}selectHost方法的核心逻辑是从ServiceInfo中获取实例列表,然后调用getHostByRandomWeight方法:protectedstaticInstancegetHostByRandomWeight(Listhosts){//...判断逻辑//重新组织数据格式List>hostsWithWeight=newArrayList>();for(Instancehost:hosts){if(host.isHealthy()){hostsWithWeight.add(newPair(host,host.getWeight()));}}//通过Chooser实现随机权重负载均衡算法ChooservipChooser=newChooser("www.taobao.com");vipChooser.refresh(hostsWithWeight);returnvipChooser.randomWithWeight();}getHostByRandomWeight的前半部分是将Instance列表及其权重数据转换成Pair,即建立pair关系。在此过程中只使用健康的节点。算法的真正实现是通过Chooser类实现的。从名字就基本知道实现的策略是基于权重的随机算法。负载均衡算法实现所有的负载均衡算法实现都位于Chooser类中,该类提供了refresh和randomWithWeight两个方法。刷新方法用于过滤数据,检查数据合法性,建立算法所需的数据模型。randomWithWeight方法根据之前的数据进行随机算法处理。先看刷新方法:publicvoidrefresh(List>itemsWithWeight){RefnewRef=newRef(itemsWithWeight);//准备数据,校验数据newRef.refresh();//上面数据刷新后,这里重新初始化一个GenericPollernewRef.poller=this.ref.poller.refresh(newRef.items);this.ref=newRef;}基本步骤:创建一个Ref类,它是的内部类选择器;调用Ref的refresh方法,用于准备数据、校验数据等,数据筛选完成后,调用poller#refresh方法,本质上是创建一个GenericPoller对象;重新分配成员变量;这里重点关注Ref#refresh方法:/***获取参与计算的实例列表,计算递增的数组个数的总和并校验*/publicvoidrefresh(){//实例权重总和DoubleoriginWeightSum=(double)0;//所有健康权重总和for(Pairitem:itemsWithWeight){doubleweight=item.weight();//ignoreitemwhichweightiszero.seetest_randomWithWeight_weight0inChooserTest//权重小于等于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;}double[]exactWeights=newdouble[items.size()];intindex=0;//计算每个节点的权重比例,放入数组for(Pairitem:itemsWithWeight){doublesingleWeight=item.weight();//忽略itemwhichweightiszero.seetest_randomWithWeight_weight0inChooserTestif(singleWeight<=0){continue;}//计算每个节点的权重比例exactWeights[index++]=singleWeight/originWeightSum;}//初始化增量arrayweights=newdouble[items.size()];doublerandomRange=0D;for(inti=0;iref=this.ref;//生成一个0-1之间的随机数doublerandom=ThreadLocalRandom.current().nextDouble(0,1);//用二分法在数组中查找指定值,如果不存在则返回(-(插入点)-1),插入点为随机数将插入数组的位置,即大于该键的第一个元素的索引。intindex=Arrays.binarySearch(ref.weights,random);//如果没有查询(返回-1或“-插入点”)if(index<0){index=-index-1;}else{//hit直接返回结果returnref.items.get(index);}//判断坐标没有越界if(index