Nacos不仅提供服务注册与发现功能,还内置了强大的负载均衡能力。Nacos的负载均衡机制主要应用于服务消费者从服务注册中心获取服务实例列表后,如何选择其中一个实例进行调用的过程。
这篇文章我们将探讨负载均衡 ,我们希望达成以下具体的目标:
✔️ 客户端负载均衡:与传统的服务端负载均衡(如Nginx)不同,Nacos实现的是客户端负载均衡。
✔️ 多种均衡策略:支持多种负载均衡算法,可扩展。
✔️ 健康检查机制:自动过滤不健康实例。
✔️ 权重支持:支持基于权重的流量分配。
1️⃣ 服务消费者从Nacos Server获取服务实例列表。
2️⃣ 客户端根据负载均衡策略选择一个实例。
3️⃣ 向选定的实例发起请求。
4️⃣ 记录调用结果。
public Server choose(ILoadBalancer lb, Object key) {
Server server = null;
while (server == null) {
// 1. 获取可用实例列表(健康状态)
List upList = lb.getReachableServers();
// 2. 获取全量实例列表(包括不健康实例)
List allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) { // 无实例直接返回
return null;
}
// 3. 生成随机索引(全量列表范围)
int index = rand.nextInt(serverCount);
// 4. 从可用列表中获取实例(可能为null)
server = upList.get(index);
}
return server;
}
随机选择逻辑
在allList
的范围内生成随机索引(index = rand.nextInt(serverCount)
),确保每个实例理论上有均等的被选概率。
upList
与allList
不一致(如部分实例不健康),直接通过upList.get(index)
可能导致:while (server == null)
重试,但可能陷入死循环(需依赖外部中断)。upList
范围 → 抛出IndexOutOfBoundsException
。设计缺陷与改进
问题:未显式处理upList与allList的差异,可能选到不健康实例。
修复方案:应在upList范围内生成随机索引(如下方优化代码)。
public Server choose(ILoadBalancer lb, Object key) {
List upList = lb.getReachableServers();
if (upList.isEmpty()) {
return null; // 无健康实例直接返回
}
int index = rand.nextInt(upList.size()); // 仅在健康实例中随机
return upList.get(index);
}
public Server choose(ILoadBalancer lb, Object key) {
Server server = null;
int count = 0; // 重试计数器(避免无限循环)
while (server == null && count++ < 10) {
// 1. 获取实例列表
List reachableServers = lb.getReachableServers(); // 健康实例
List allServers = lb.getAllServers(); // 全量实例
int upCount = reachableServers.size();
int serverCount = allServers.size();
// 2. 空列表检查
if ((upCount == 0) || (serverCount == 0)) {
return null;
}
// 3. 计算下一个实例索引(原子递增取模)
int nextServerIndex = incrementAndGetModulo(serverCount);
server = allServers.get(nextServerIndex);
// 4. 有效性检查
if (server == null) {
Thread.yield(); // 让出CPU避免忙等待
continue;
}
// 5. 健康检查(需实例实现isAlive等方法)
if (server.isAlive() && server.isReadyToServe()) {
return server;
}
server = null; // 实例不健康则重试
}
return server; // 超过重试次数返回null或最后尝试的实例
}
incrementAndGetModulo()
:通过原子操作(AtomicInteger)递增索引并取模,确保线程安全。isAlive()
和isReadyToServe()
:需服务实例实现这些方法(如Nacos实例默认健康状态由ServerListFilter维护)。Thread.yield()
:在竞争激烈时让出CPU。
java
public Instance chooseInstance(List instances) {
// 根据权重值进行选择
if (CollectionUtils.isEmpty(instances)) {
return null;
}
double[] weights = new double[instances.size()];
double totalWeight = 0;
for (int i = 0; i < instances.size(); i++) {
weights[i] = instances.get(i).getWeight();
totalWeight += weights[i];
}
double randomWeight = random.nextDouble() * totalWeight;
double tempWeight = 0;
for (int i = 0; i < weights.length; i++) {
tempWeight += weights[i];
if (randomWeight <= tempWeight) {
return instances.get(i);
}
}
return instances.get(0);
}
Nacos的权重算法基于加权随机(Weighted Random)实现,通过为每个服务实例分配权重值,控制流量分配比例。其核心逻辑如下:
策略类型 | 特点 | 使用场景 | 优缺点 |
---|---|---|---|
随机策略(RandomRule) | - 完全随机选择实例 - 实现简单 - 无状态 |
- 实例性能均匀的环境 - 快速验证场景 - 无特殊路由要求的服务调用 |
✅ 优点:实现简单,无状态 ❌ 缺点:无法考虑实例负载差异,可能不均匀 |
轮询策略(RoundRobinRule) | - 按顺序依次选择 - 均匀分配请求 - 无权重考虑 |
- 实例配置相同的集群 - 需要严格均匀分配的场景 - 无状态服务 |
✅ 优点:请求分配绝对均匀 ❌ 缺点:无法应对性能差异的实例 |
权重策略(NacosWeightedRule) | - 根据控制台配置的权重分配 - 支持动态调整 - 考虑实例性能差异 |
- 实例配置不均的环境 - 灰度发布 - 金丝雀发布 - 根据硬件性能分配流量 |
✅ 优点:最灵活,支持动态调整 ❌ 缺点:需要合理设置权重值 |
Nacos的负载均衡通过客户端动态决策和服务端健康管理的结合,实现了高可用与灵活性。其核心价值在于:
对于微服务架构,合理选择负载均衡策略并持续监控调优,是提升系统弹性和性能的关键。