从零带你手写Java七种负载均衡算法实现方案
在分布式系统、微服务架构以及高并发场景中,负载均衡(Load Balancing) 是一项至关重要的技术。它能够将请求合理地分发到多个服务节点上,从而提升系统整体的吞吐量、可用性和容错能力。
本文将带你纯手撸实现七种常见的负载均衡算法,全部使用 Java 编写,不依赖任何第三方框架,帮助你深入理解其核心原理与适用场景。
准备工作
首先定义一个通用的服务节点接口:
public class Server {
private String host;
private int port;
private int weight; // 权重,用于加权类算法
public Server(String host, int port) {
this(host, port, 1);
}
public Server(String host, int port, int weight) {
this.host = host;
this.port = port;
this.weight = weight;
}
// getters & setters
public String getHost() { return host; }
public int getPort() { return port; }
public int getWeight() { return weight; }
public void setWeight(int weight) { this.weight = weight; }
@Override
public String toString() {
return host + ":" + port + "(w=" + weight + ")";
}
}
所有算法都将实现以下接口:
public interface LoadBalancer {
Server select(List<Server> servers);
}
1. 随机(Random)
最简单的策略:从可用节点中随机选择一个。
public class RandomLoadBalancer implements LoadBalancer {
private final Random random = new Random();
@Override
public Server select(List<Server> servers) {
if (servers == null || servers.isEmpty()) return null;
int index = random.nextInt(servers.size());
return servers.get(index);
}
}
优点:简单、无状态
缺点:无法保证请求分布均匀(尤其在短时间窗口内)
2. 轮询(Round Robin)
按顺序依次选择节点,循环往复。
public class RoundRobinLoadBalancer implements LoadBalancer {
private AtomicInteger index = new AtomicInteger(0);
@Override
public Server select(List<Server> servers) {
if (servers == null || servers.isEmpty()) return null;
int i = index.getAndIncrement() % servers.size();
// 处理负数(虽然 unlikely)
if (i < 0) i += servers.size();
return servers.get(i);
}
}
优点:请求分布均匀
缺点:未考虑服务器性能差异
3. 加权轮询(Weighted Round Robin)
为每个节点分配权重,高权重节点被选中的频率更高。
实现思路:采用“最大公约数 + 当前轮次”方式,避免预生成列表(节省内存)。
public class WeightedRoundRobinLoadBalancer implements LoadBalancer {
private AtomicInteger currentPos = new AtomicInteger(0);
@Override
public Server select(List<Server> servers) {
if (servers == null || servers.isEmpty()) return null;
// 计算总权重
int totalWeight = servers.stream().mapToInt(Server::getWeight).sum();
if (totalWeight <= 0) {
// 退化为普通轮询
return new RoundRobinLoadBalancer().select(servers);
}
int current = currentPos.getAndIncrement() % totalWeight;
if (current < 0) current += totalWeight;
// 遍历找到对应节点
for (Server server : servers) {
if (current < server.getWeight()) {
return server;
}
current -= server.getWeight();
}
// 理论上不会走到这里
return servers.get(servers.size() - 1);
}
}
注意:上述实现是简化版。工业级实现(如 Nginx)通常使用更复杂的平滑加权轮询(Smooth Weighted Round Robin),以避免连续选中高权重节点。
4. 平滑加权轮询(Smooth Weighted Round Robin)
由 Nginx 提出,解决加权轮询中“高权重节点连续被选中”的问题。
public class SmoothWeightedRoundRobinLoadBalancer implements LoadBalancer {
private final Map<Server, Integer> currentWeights = new ConcurrentHashMap<>();
@Override
public Server select(List<Server> servers) {
if (servers == null || servers.isEmpty()) return null;
int totalWeight = 0;
Server best = null;
int max = Integer.MIN_VALUE;
for (Server server : servers) {
int weight = server.getWeight();
if (weight <= 0) weight = 1;
totalWeight += weight;
int current = currentWeights.getOrDefault(server, 0) + weight;
currentWeights.put(server, current);
if (current > max) {
max = current;
best = server;
}
}
if (best != null) {
currentWeights.put(best, max - totalWeight);
}
return best;
}
}
优点:权重分配更平滑,高权重节点不会连续被选中
示例:A(w=5), B(w=1) → 顺序为 A,A,A,A,A,B,... 而非 A,A,A,A,A,A,...
5. 最少连接(Least Connections)
将请求分发给当前连接数最少的节点。
为简化,我们用一个 Map<Server, AtomicInteger> 模拟连接计数。
public class LeastConnectionsLoadBalancer implements LoadBalancer {
private final Map<Server, AtomicInteger> connectionCounts = new ConcurrentHashMap<>();
@Override
public Server select(List<Server> servers) {
if (servers == null || servers.isEmpty()) return null;
Server best = null;
int minConn = Integer.MAX_VALUE;
for (Server server : servers) {
int conn = connectionCounts.computeIfAbsent(server, k -> new AtomicInteger(0)).get();
if (conn < minConn) {
minConn = conn;
best = server;
}
}
// 模拟增加连接(实际使用需配合请求完成后的 decrement)
if (best != null) {
connectionCounts.get(best).incrementAndGet();
}
return best;
}
// 供外部调用:请求完成后减少连接数
public void release(Server server) {
AtomicInteger count = connectionCounts.get(server);
if (count != null) {
count.decrementAndGet();
}
}
}
适用于长连接或处理时间差异大的场景
需要维护连接状态,有额外开销
6. 源地址哈希(IP Hash / Source Hash)
根据客户端 IP(或其他唯一标识)做哈希,保证同一客户端始终路由到同一节点。
public class SourceHashLoadBalancer implements LoadBalancer {
private String source; // 可通过构造函数传入 client IP
public SourceHashLoadBalancer(String source) {
this.source = source;
}
@Override
public Server select(List<Server> servers) {
if (servers == null || servers.isEmpty() || source == null) return null;
int hash = source.hashCode();
int index = (hash & 0x7FFFFFFF) % servers.size(); // 避免负数
return servers.get(index);
}
}
优点:会话保持(Session Stickiness)
缺点:节点增减会导致大量映射失效(可改用一致性哈希)
7. 一致性哈希(Consistent Hashing)
解决普通哈希在节点动态变化时缓存/会话大量失效的问题。
public class ConsistentHashingLoadBalancer implements LoadBalancer {
private final SortedMap<Integer, Server> circle = new TreeMap<>();
private final int virtualNodes; // 虚拟节点数
public ConsistentHashingLoadBalancer(int virtualNodes) {
this.virtualNodes = virtualNodes;
}
public void addServer(Server server) {
for (int i = 0; i < virtualNodes; i++) {
int hash = hash(server.getHost() + ":" + server.getPort() + "#" + i);
circle.put(hash, server);
}
}
public void removeServer(Server server) {
for (int i = 0; i < virtualNodes; i++) {
int hash = hash(server.getHost() + ":" + server.getPort() + "#" + i);
circle.remove(hash);
}
}
private int hash(String key) {
return key.hashCode(); // 简化,生产建议用 MD5 或 MurmurHash
}
@Override
public Server select(List<Server> servers) {
if (circle.isEmpty()) {
// 动态构建环(实际应提前构建)
servers.forEach(this::addServer);
}
if (circle.isEmpty()) return null;
// 假设 source 为请求 ID 或 IP
String requestKey = "request_" + System.nanoTime(); // 实际应由调用方提供
int hash = hash(requestKey);
if (!circle.containsKey(hash)) {
SortedMap<Integer, Server> tailMap = circle.tailMap(hash);
hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
}
return circle.get(hash);
}
}
节点增减只影响局部数据
广泛用于缓存、分布式存储系统
总结对比
| 算法 | 是否考虑权重 | 是否有状态 | 适用场景 |
|---|---|---|---|
| 随机 | ❌ | ❌ | 简单快速分发 |
| 轮询 | ❌ | ✅(位置) | 请求均匀、节点同质 |
| 加权轮询 | ✅ | ✅ | 节点性能不同 |
| 平滑加权轮询 | ✅ | ✅ | 更公平的加权分发 |
| 最少连接 | ❌(但看负载) | ✅ | 长连接、异构任务 |
| 源地址哈希 | ❌ | ❌ | 会话保持 |
| 一致性哈希 | ❌(可扩展支持) | ✅(哈希环) | 缓存、分布式存储 |
结语
通过手写这七种负载均衡算法,我们不仅掌握了其实现细节,也理解了它们各自的优劣和适用边界。在真实项目中,可根据业务需求灵活选择或组合使用(例如:先一致性哈希定位节点组,再在组内轮询)。
提示:生产环境建议使用成熟组件(如 Ribbon、Spring Cloud LoadBalancer、Nginx、LVS),但理解底层原理永远是工程师的核心竞争力。
以上就是从零带你手写Java七种负载均衡算法实现方案的详细内容,更多关于Java负载均衡算法的资料请关注脚本之家其它相关文章!


最新评论