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、服务节点的信息是存储在数据库,存在直接修改数据库的可能,所以这边做了两层校验,即前端保存时的检验和进行服务发现时的校验。