4. 静态负载均衡算法:轮询、加权轮询、源地址哈希、随机算法的实现与优缺点
静态负载均衡算法,说白了就是“事先定好规则,运行时照着办”。它不关心后端服务器的实时负载、响应时间,也不管CPU是不是已经飙到90%。规则写死了,请求来了就按规则分配。
我刚开始做分布式系统时,觉得静态算法太“笨”了。后来在某个高并发项目里,动态算法因为频繁采集指标反而把监控系统打挂了……嗯,从那以后我明白了:简单,有时候就是最大的优势。
核心观点:静态算法适合后端服务能力均匀、请求处理成本相近的场景。它没有额外开销,性能极高,是很多网关和代理的默认选择。
4.1 轮询算法 (Round Robin)
轮询是最基础的算法。请求按顺序依次分配给后端服务器,循环往复。就像打牌时轮流发牌,每人一张,公平得很。
// 轮询算法核心实现
public class RoundRobin {
private List<String> servers;
private int currentIndex = 0;
public RoundRobin(List<String> servers) {
this.servers = servers;
}
public String getNextServer() {
// 原子操作,防止并发问题
int index = currentIndex;
currentIndex = (currentIndex + 1) % servers.size();
return servers.get(index);
}
}
我的经验:轮询算法在服务器配置完全一致时效果最好。我曾经在一个项目里用轮询,结果发现某台老机器总是先挂掉——后来一查,那台机器的内存只有其他机器的一半。轮询不会考虑这些差异,它只管“轮流”。
| 维度 | 说明 |
|---|---|
| 优点 | 实现简单,无状态,请求分配均匀 |
| 缺点 | 不考虑服务器性能差异,慢服务器容易成为瓶颈 |
| 适用场景 | 服务器配置相同、请求处理成本相近的集群 |
4.2 加权轮询算法 (Weighted Round Robin)
加权轮询解决了轮询的“一刀切”问题。给每台服务器设置一个权重,权重高的服务器分到更多请求。说白了,就是“能者多劳”。
// 加权轮询算法核心实现
public class WeightedRoundRobin {
private List<Server> servers;
private int currentIndex = 0;
private int currentWeight = 0;
static class Server {
String ip;
int weight; // 权重,如 5、3、2
}
public String getNextServer() {
while (true) {
currentIndex = (currentIndex + 1) % servers.size();
if (currentIndex == 0) {
currentWeight--;
if (currentWeight <= 0) {
currentWeight = getMaxWeight();
}
}
if (servers.get(currentIndex).weight >= currentWeight) {
return servers.get(currentIndex).ip;
}
}
}
}
我曾经踩过的坑:权重设置不是线性的。权重5和权重3,不是说性能比是5:3,而是请求分配比例是5:3。如果服务器性能差异很大,建议先压测,再根据实际QPS来定权重。我见过有人拍脑袋设权重,结果高权重服务器被打爆了。
| 维度 | 说明 |
|---|---|
| 优点 | 支持异构服务器,分配比例可控 |
| 缺点 | 权重需要人工维护,无法动态调整 |
| 适用场景 | 服务器配置不同、需要按能力分配流量的集群 |
4.3 源地址哈希算法 (IP Hash)
源地址哈希,就是对客户端IP做哈希计算,然后取模得到服务器索引。同一个IP的请求,永远落在同一台服务器上。你想想看,这天然支持了会话保持,不需要额外的Session共享机制。
// 源地址哈希算法核心实现
public class IpHash {
private List<String> servers;
public IpHash(List<String> servers) {
this.servers = servers;
}
public String getServer(String clientIp) {
// 使用客户端的IP地址进行哈希
int hashCode = clientIp.hashCode();
// 取值,防止负数
int index = Math.abs(hashCode) % servers.size();
return servers.get(index);
}
}
注意:哈希算法的均匀性很重要。我建议使用一致性哈希(Consistent Hash)来替代简单的取模哈希,这样当服务器扩缩容时,只有少量请求需要重新分配,不会引发“缓存雪崩”。
| 维度 | 说明 |
|---|---|
| 优点 | 天然会话保持,实现简单 |
| 缺点 | 服务器扩缩容会导致大量请求重新分配;可能造成负载不均 |
| 适用场景 | 需要会话粘滞、有状态服务的场景 |
4.4 随机算法 (Random)
随机算法,就是从服务器列表中随机选一台。听起来很不靠谱?其实在大流量下,随机算法的分布非常接近均匀。而且它实现最简单,连计数器都不需要。
// 随机算法核心实现
public class RandomLoadBalance {
private List<String> servers;
private Random random = new Random();
public RandomLoadBalance(List<String> servers) {
this.servers = servers;
}
public String getServer() {
int index = random.nextInt(servers.size());
return servers.get(index);
}
}
我个人习惯:在压测或临时扩容时,我经常用随机算法。因为它不需要维护任何状态,扩缩容时只要更新服务器列表就行。但要注意,如果服务器数量很少(比如2台),随机算法可能短期出现明显不均,这时候轮询更靠谱。
| 维度 | 说明 |
|---|---|
| 优点 | 实现最简单,无状态,扩缩容影响小 |
| 缺点 | 小流量下分布不均,无法保证会话保持 |
| 适用场景 | 大流量、无状态服务、临时扩容场景 |
4.5 四种算法对比总结
说了这么多,到底怎么选?我整理了一张对比表,你一看就明白。
| 算法 | 实现复杂度 | 负载均匀性 | 会话保持 | 扩缩容影响 | 推荐场景 |
|---|---|---|---|---|---|
| 轮询 | ★☆☆☆☆ | ★★★★★ | 不支持 | 小 | 同配置服务器集群 |
| 加权轮询 | ★★☆☆☆ | ★★★★☆ | 不支持 | 小 | 异构服务器集群 |
| 源地址哈希 | ★★☆☆☆ | ★★★☆☆ | 支持 | 大 | 有状态服务 |
| 随机 | ★☆☆☆☆ | ★★★★☆ | 不支持 | 极小 | 大流量无状态服务 |
最后提醒一句:静态算法虽然简单高效,但有一个致命缺陷——它不感知后端服务器的健康状态。如果某台服务器挂了,静态算法依然会把请求发过去。所以实际生产环境中,静态算法一定要配合健康检查机制使用。我见过有人只用轮询,结果一台服务器宕机了,整个集群的失败率直接飙升到25%。