在分布式系统、微服务架构以及高并发场景中,负载均衡(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负载均衡算法的资料请关注代码网其它相关文章!
发表评论