nacos负载均衡
NacosRule是AlibabaNacos自己实现的一个负载均衡策略,可以在nacos平台中根据自定义权重进行访问。
源码 NacosRule
- NacosRule 继承 AbstractLoadBalancerRule,其他负载均衡规则如RoundRobinRule,也是继承此抽象类。
- 重点是choose方法,可以通过debug进行查看。
public class NacosRule extends AbstractLoadBalancerRule {
private static final Logger LOGGER = LoggerFactory.getLogger(NacosRule.class);
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Autowired
private NacosServiceManager nacosServiceManager;
public NacosRule() {
}
public Server choose(Object key) {
try {
//nacos集群名称,默认为DEAULT
String clusterName = this.nacosDiscoveryProperties.getClusterName();
// 服务分组名称,老版本没有这个,所以服务不在默认分组中,自定义权重无效
String group = this.nacosDiscoveryProperties.getGroup();
//拿到服务提供者集群信息
DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer)this.getLoadBalancer();
//服务提供者服务名
String name = loadBalancer.getName();
//nacos的信息,里面包含服务提供者和调用者信息,还有一些缓存信息
NamingService namingService = this.nacosServiceManager.getNamingService(this.nacosDiscoveryProperties.getNacosProperties());
//根据当前服务group拿到服务提供者的可用实例信息,所以,不在一个group的服务不行
List<Instance> instances = namingService.selectInstances(name, group, true);
if (CollectionUtils.isEmpty(instances)) {
LOGGER.warn("no instance in service {}", name);
return null;
} else {
List<Instance> instancesToChoose = instances;
if (StringUtils.isNotBlank(clusterName)) {
//拿到同一个集群的服务提供者实例
List<Instance> sameClusterInstances = (List)instances.stream().filter((instancex) -> {
return Objects.equals(clusterName, instancex.getClusterName());
}).collect(Collectors.toList());
if (!CollectionUtils.isEmpty(sameClusterInstances)) {
instancesToChoose = sameClusterInstances;
} else {
LOGGER.warn("A cross-cluster call occurs,name = {}, clusterName = {}, instance = {}", new Object[]{name, clusterName, instances});
}
}
//通过算法返回调用的实例
Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToChoose);
return new NacosServer(instance);
}
} catch (Exception var10) {
LOGGER.warn("NacosRule error", var10);
return null;
}
}
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
}
算法核心 ExtendBalancer.getHostByRandomWeight2(instancesToChoose);
protected static Instance getHostByRandomWeight(List<Instance> hosts) {
NAMING_LOGGER.debug("entry randomWithWeight");
if (hosts == null || hosts.size() == 0) {
NAMING_LOGGER.debug("hosts == null || hosts.size() == 0");
return null;
}
NAMING_LOGGER.debug("new Chooser");
List<Pair<Instance>> hostsWithWeight = new ArrayList<Pair<Instance>>();
//拿出健康的实例
for (Instance host : hosts) {
if (host.isHealthy()) {
hostsWithWeight.add(new Pair<Instance>(host, host.getWeight()));
}
}
NAMING_LOGGER.debug("for (Host host : hosts)");
//核心算法
Chooser<String, Instance> vipChooser = new Chooser<String, Instance>("www.taobao.com");
vipChooser.refresh(hostsWithWeight);
NAMING_LOGGER.debug("vipChooser.refresh");
return vipChooser.randomWithWeight();
}
}
refresh方法
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.");
}
以上算法如图:
图来自:nacos源码
通过上面的算法得出一个ref,其中包含节点信息和权重数据
public T randomWithWeight() {
Ref<T> ref = this.ref;
//获取一个随机数
double random = ThreadLocalRandom.current().nextDouble(0, 1);
//ref.weights是根据nacos配置的权重得出的数组,进行二分查找,判断在哪个范围
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) {
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);
}
以上