权重算法
通过学习Nacos,在nacos中源码发现NamingService.selectOneHealthyInstance的权重算法。
基础含义
权重是一个相对的概念,是针对某一指标而言。某一指标的权重是指该指标在整体评价中的相对重要程度。
如:学生期末总评是对学生平时成绩,期中考成绩,期末考成绩的综合评价,但是这三个成绩所占期末总评的成绩的比重不一样。若平时成绩占30%,期中考成绩占30%,期末考成绩占40%,那么期末总评=平时成绩0.3+期中考成绩0.3+期末考成绩0.4。
再打个比方说, 一件事情, 你给它打100分, 你的老板给它打60分, 如果平均, 则是(100+60)/2=80分. 但因为老板说的话分量比你重, 假如老板的权重是2, 你是1, 这时求平均值就是加权平均了, 结果是(1001 + 602)/(1+2)=73.3分, 显然向你的老板那里倾斜了。假如老板权重是1,你的权重是3,结果是(1003+60*1)/(1+3)=90。这就是根据权重的不同进行的平均数的计算,所以又叫加权平均数
在源码中,我们发现 的方法是 refresh()以及randomWithWeight()
protected static Instance getHostByRandomWeight(List<Instance> hosts) {
LogUtils.NAMING_LOGGER.debug("entry randomWithWeight");
if (hosts != null && hosts.size() != 0) {
LogUtils.NAMING_LOGGER.debug("new Chooser");
List<Pair<Instance>> hostsWithWeight = new ArrayList();
Iterator var2 = hosts.iterator();
while(var2.hasNext()) {
Instance host = (Instance)var2.next();
if (host.isHealthy()) {
hostsWithWeight.add(new Pair(host, host.getWeight()));
}
}
LogUtils.NAMING_LOGGER.debug("for (Host host : hosts)");
Chooser<String, Instance> vipChooser = new Chooser("www.taobao.com");
vipChooser.refresh(hostsWithWeight);
LogUtils.NAMING_LOGGER.debug("vipChooser.refresh");
return (Instance)vipChooser.randomWithWeight();
} else {
LogUtils.NAMING_LOGGER.debug("hosts == null || hosts.size() == 0");
return null;
}
}
public void refresh() {
Double originWeightSum = 0.0D;
Iterator var2 = this.itemsWithWeight.iterator();
double weight;
while(var2.hasNext()) {
Pair<T> item = (Pair)var2.next();
weight = item.weight();
if (weight > 0.0D) {
this.items.add(item.item());
if (Double.isInfinite(weight)) {
weight = 10000.0D;
}
if (Double.isNaN(weight)) {
weight = 1.0D;
}
originWeightSum = originWeightSum + weight;
}
}
double[] exactWeights = new double[this.items.size()];
int index = 0;
Iterator var10 = this.itemsWithWeight.iterator();
double doublePrecisionDelta;
while(var10.hasNext()) {
Pair<T> itemx = (Pair)var10.next();
doublePrecisionDelta = itemx.weight();
if (doublePrecisionDelta > 0.0D) {
exactWeights[index++] = doublePrecisionDelta / originWeightSum;
}
}
this.weights = new double[this.items.size()];
weight = 0.0D;
for(int i = 0; i < index; ++i) {
this.weights[i] = weight + exactWeights[i];
weight += exactWeights[i];
}
doublePrecisionDelta = 1.0E-4D;
if (index != 0 && Math.abs(this.weights[index - 1] - 1.0D) >= doublePrecisionDelta) {
throw new IllegalStateException("Cumulative Weight caculate wrong , the sum of probabilities does not equals 1.");
}
}
public T randomWithWeight() {
Chooser<K, T>.Ref<T> ref = this.ref;
double random = ThreadLocalRandom.current().nextDouble(0.0D, 1.0D);
int index = Arrays.binarySearch(ref.weights, random);
if (index < 0) {
index = -index - 1;
return index >= 0 && index < ref.weights.length && random < ref.weights[index] ? ref.items.get(index) : ref.items.get(ref.items.size() - 1);
} else {
return ref.items.get(index);
}
}
refresh()方法的主要作用是计算出一个数组,将每个权重的比例计算出来,如数组A[1,2,0,3],执行refresh(),去除小于等于0的数,得到B[1,2,3],再得到权重比例为[1/6,3/6,1]。至于为什么是3/6包含,可以看下
this.weights = new double[this.items.size()];
weight = 0.0D;
for(int i = 0; i < index; ++i) {
this.weights[i] = weight + exactWeights[i];
weight += exactWeights[i];
}
可以发现,当i=1的时候,B[1]=A[0]+A[1]j,即1/6+2/6=3/6
理解到这里,作者当时存在疑惑,无论最后一个的权重是多大多小,都为1。这怎么就有根据权重算了?看到这里解除了我的疑惑
//因为得到的B数组,肯定是大于0小于1,从0-1获取一个随机数
double random = ThreadLocalRandom.current().nextDouble(0.0D, 1.0D);
//根据二分法,查找到 random 的散落在0-1的范围
int index = Arrays.binarySearch(ref.weights, random);
这时,我恍然大悟,原来 根据权重的算法,说到底,就是散落区间的概率。