從零帶你手寫Java七種負載均衡算法實現(xiàn)方案
在分布式系統(tǒng)、微服務架構(gòu)以及高并發(fā)場景中,負載均衡(Load Balancing) 是一項至關(guān)重要的技術(shù)。它能夠?qū)⒄埱蠛侠淼胤职l(fā)到多個服務節(jié)點上,從而提升系統(tǒng)整體的吞吐量、可用性和容錯能力。
本文將帶你純手擼實現(xiàn)七種常見的負載均衡算法,全部使用 Java 編寫,不依賴任何第三方框架,幫助你深入理解其核心原理與適用場景。
準備工作
首先定義一個通用的服務節(jié)點接口:
public class Server {
private String host;
private int port;
private int weight; // 權(quán)重,用于加權(quán)類算法
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 + ")";
}
}
所有算法都將實現(xiàn)以下接口:
public interface LoadBalancer {
Server select(List<Server> servers);
}
1. 隨機(Random)
最簡單的策略:從可用節(jié)點中隨機選擇一個。
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);
}
}
優(yōu)點:簡單、無狀態(tài)
缺點:無法保證請求分布均勻(尤其在短時間窗口內(nèi))
2. 輪詢(Round Robin)
按順序依次選擇節(jié)點,循環(huán)往復。
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();
// 處理負數(shù)(雖然 unlikely)
if (i < 0) i += servers.size();
return servers.get(i);
}
}
優(yōu)點:請求分布均勻
缺點:未考慮服務器性能差異
3. 加權(quán)輪詢(Weighted Round Robin)
為每個節(jié)點分配權(quán)重,高權(quán)重節(jié)點被選中的頻率更高。
實現(xiàn)思路:采用“最大公約數(shù) + 當前輪次”方式,避免預生成列表(節(jié)省內(nèi)存)。
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;
// 計算總權(quán)重
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;
// 遍歷找到對應節(jié)點
for (Server server : servers) {
if (current < server.getWeight()) {
return server;
}
current -= server.getWeight();
}
// 理論上不會走到這里
return servers.get(servers.size() - 1);
}
}
注意:上述實現(xiàn)是簡化版。工業(yè)級實現(xiàn)(如 Nginx)通常使用更復雜的平滑加權(quán)輪詢(Smooth Weighted Round Robin),以避免連續(xù)選中高權(quán)重節(jié)點。
4. 平滑加權(quán)輪詢(Smooth Weighted Round Robin)
由 Nginx 提出,解決加權(quán)輪詢中“高權(quán)重節(jié)點連續(xù)被選中”的問題。
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;
}
}
優(yōu)點:權(quán)重分配更平滑,高權(quán)重節(jié)點不會連續(xù)被選中
示例:A(w=5), B(w=1) → 順序為 A,A,A,A,A,B,... 而非 A,A,A,A,A,A,...
5. 最少連接(Least Connections)
將請求分發(fā)給當前連接數(shù)最少的節(jié)點。
為簡化,我們用一個 Map<Server, AtomicInteger> 模擬連接計數(shù)。
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;
}
// 供外部調(diào)用:請求完成后減少連接數(shù)
public void release(Server server) {
AtomicInteger count = connectionCounts.get(server);
if (count != null) {
count.decrementAndGet();
}
}
}
適用于長連接或處理時間差異大的場景
需要維護連接狀態(tài),有額外開銷
6. 源地址哈希(IP Hash / Source Hash)
根據(jù)客戶端 IP(或其他唯一標識)做哈希,保證同一客戶端始終路由到同一節(jié)點。
public class SourceHashLoadBalancer implements LoadBalancer {
private String source; // 可通過構(gòu)造函數(shù)傳入 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(); // 避免負數(shù)
return servers.get(index);
}
}
優(yōu)點:會話保持(Session Stickiness)
缺點:節(jié)點增減會導致大量映射失效(可改用一致性哈希)
7. 一致性哈希(Consistent Hashing)
解決普通哈希在節(jié)點動態(tài)變化時緩存/會話大量失效的問題。
public class ConsistentHashingLoadBalancer implements LoadBalancer {
private final SortedMap<Integer, Server> circle = new TreeMap<>();
private final int virtualNodes; // 虛擬節(jié)點數(shù)
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(); // 簡化,生產(chǎn)建議用 MD5 或 MurmurHash
}
@Override
public Server select(List<Server> servers) {
if (circle.isEmpty()) {
// 動態(tài)構(gòu)建環(huán)(實際應提前構(gòu)建)
servers.forEach(this::addServer);
}
if (circle.isEmpty()) return null;
// 假設 source 為請求 ID 或 IP
String requestKey = "request_" + System.nanoTime(); // 實際應由調(diào)用方提供
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);
}
}
節(jié)點增減只影響局部數(shù)據(jù)
廣泛用于緩存、分布式存儲系統(tǒng)
總結(jié)對比
| 算法 | 是否考慮權(quán)重 | 是否有狀態(tài) | 適用場景 |
|---|---|---|---|
| 隨機 | ? | ? | 簡單快速分發(fā) |
| 輪詢 | ? | ?(位置) | 請求均勻、節(jié)點同質(zhì) |
| 加權(quán)輪詢 | ? | ? | 節(jié)點性能不同 |
| 平滑加權(quán)輪詢 | ? | ? | 更公平的加權(quán)分發(fā) |
| 最少連接 | ?(但看負載) | ? | 長連接、異構(gòu)任務 |
| 源地址哈希 | ? | ? | 會話保持 |
| 一致性哈希 | ?(可擴展支持) | ?(哈希環(huán)) | 緩存、分布式存儲 |
結(jié)語
通過手寫這七種負載均衡算法,我們不僅掌握了其實現(xiàn)細節(jié),也理解了它們各自的優(yōu)劣和適用邊界。在真實項目中,可根據(jù)業(yè)務需求靈活選擇或組合使用(例如:先一致性哈希定位節(jié)點組,再在組內(nèi)輪詢)。
提示:生產(chǎn)環(huán)境建議使用成熟組件(如 Ribbon、Spring Cloud LoadBalancer、Nginx、LVS),但理解底層原理永遠是工程師的核心競爭力。
以上就是從零帶你手寫Java七種負載均衡算法實現(xiàn)方案的詳細內(nèi)容,更多關(guān)于Java負載均衡算法的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring的連接數(shù)據(jù)庫以及JDBC模板(實例講解)
下面小編就為大家?guī)硪黄猄pring的連接數(shù)據(jù)庫以及JDBC模板(實例講解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-10-10
關(guān)于JFormDesigner的安裝及破姐超詳細教程
JFormDesigner是一種先進的圖形用戶界面Swing?的設計工具(非開源),具有一個獨立的開發(fā)工具產(chǎn)品和基于不同開發(fā)工具如Eclipse、NetBeans等的開發(fā)插件,本文給大家介紹JFormDesigner安裝破解教程,感興趣的朋友一起看看吧2023-12-12
Spring @ExceptionHandler注解統(tǒng)一異常處理和獲取方法名
這篇文章主要介紹了Spring注解之@ExceptionHandler 統(tǒng)一異常處理和獲取方法名,在實際項目中,合理使用@ExceptionHandler能夠提高代碼的可維護性和用戶體驗,通過本文的解析和實踐,讀者可以更好地理解和掌握@ExceptionHandler的用法和原理2023-09-09
spring整合redis以及使用RedisTemplate的方法
本篇文章主要介紹了spring整合redis以及使用RedisTemplate的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-05-05
Java Web中常用的分頁組件(Java端實現(xiàn))
本文通過使用場景分析給大家介紹了Java Web中常用的分頁組件(Java端實現(xiàn)),非常不錯,具有參考借鑒價值,需要的朋友參考下吧2017-05-05

