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

