使用Java實(shí)現(xiàn)6種常見負(fù)載均衡算法
負(fù)載均衡是指將來(lái)自客戶端的請(qǐng)求分?jǐn)偟蕉鄠€(gè)服務(wù)器上進(jìn)行處理,從而有效地提高系統(tǒng)性能、可用性和可擴(kuò)展性。常見的負(fù)載均衡算法包括輪詢法、加權(quán)輪詢法、隨機(jī)法、加權(quán)隨機(jī)法、源地址哈希法和最小連接數(shù)法等。
在實(shí)際應(yīng)用中有很多工具和框架使用了這些算法來(lái)解決服務(wù)器負(fù)載均衡的問題。下面我整理出了一些常見的工具和框架:
Nginx
:Nginx 是一款高性能的 Web 服務(wù)器,同時(shí)也是一款反向代理服務(wù)器。Nginx 的負(fù)載均衡模塊支持多種算法,包括輪詢法、加權(quán)輪詢法、IP_HASH 等。Apache
:Apache 是一款流行的 Web 服務(wù)器。Apache 也提供了負(fù)載均衡模塊,支持多種算法,包括輪詢法、加權(quán)輪詢法、最小連接數(shù)法等。HAProxy
:HAProxy 是一款高性能的 TCP/HTTP 負(fù)載均衡器,支持多種負(fù)載均衡算法,包括輪詢法、加權(quán)輪詢法、IP_HASH、最少連接數(shù)法、最短響應(yīng)時(shí)間法等。Spring Cloud Ribbon
:Ribbon 是一款基于 HTTP 和 TCP 客戶端的負(fù)載均衡器,是 Spring Cloud 生態(tài)系統(tǒng)中的一員。Ribbon 支持多種負(fù)載均衡算法,包括輪詢法、加權(quán)輪詢法、隨機(jī)法等。ZooKeeper
:ZooKeeper 是一款分布式協(xié)調(diào)服務(wù),在分布式系統(tǒng)中廣泛應(yīng)用。ZooKeeper 的客戶端庫(kù) Curator 提供了一種基于 ZooKeeper 的負(fù)載均衡算法,稱為 Dynamic Server List Load Balancing。
這些工具和框架都應(yīng)用了負(fù)載均衡算法來(lái)實(shí)現(xiàn)服務(wù)器的負(fù)載均衡,它們的實(shí)現(xiàn)方式也各不相同,但是盡管如此,我們可以從中學(xué)習(xí)到很多有價(jià)值的經(jīng)驗(yàn)和思路。
劃重點(diǎn) Java 架構(gòu)師必備技能-我們要深入學(xué)習(xí)各類負(fù)載均衡算法實(shí)現(xiàn)方式
Java負(fù)載均衡算法也是分布式系統(tǒng)中的重要組成部分,用于將來(lái)自客戶端的請(qǐng)求分配到不同的后端服務(wù)器上,以達(dá)到提高系統(tǒng)吞吐量、減輕服務(wù)器負(fù)擔(dān)、提高系統(tǒng)可用性等目的。本文將介紹常見的Java負(fù)載均衡算法,輪詢法、加權(quán)隨機(jī)法……一次性讓你了解 6 種常見負(fù)載均衡
算法。
一、輪詢法(Round Robin)
輪詢法是最簡(jiǎn)單、最常見的負(fù)載均衡算法之一,其實(shí)現(xiàn)思路也非常簡(jiǎn)單:按照事先規(guī)定的順序依次將請(qǐng)求轉(zhuǎn)發(fā)至后端服務(wù)器。例如,若有3臺(tái)服務(wù)器,則第1個(gè)請(qǐng)求會(huì)被分配到第1臺(tái)服務(wù)器上,第2個(gè)請(qǐng)求會(huì)被分配到第2臺(tái)服務(wù)器上,第3個(gè)請(qǐng)求會(huì)被分配到第3臺(tái)服務(wù)器上,第4個(gè)請(qǐng)求又會(huì)被分配到第1臺(tái)服務(wù)器上,以此類推。
這種算法的優(yōu)點(diǎn)是實(shí)現(xiàn)簡(jiǎn)單、可靠性高,但是它并沒有考慮服務(wù)器的實(shí)際負(fù)載情況,導(dǎo)致某些服務(wù)器可能會(huì)承受過多的負(fù)載,而其他服務(wù)器則處于空閑狀態(tài)。
輪詢法(Round Robin)寫個(gè)簡(jiǎn)單的實(shí)現(xiàn)代碼讓你感覺一下:
//定義一個(gè)全局計(jì)數(shù)器,每次調(diào)用累加 private static AtomicInteger atomicInteger = new AtomicInteger(0); //定義服務(wù)器列表 private static List<String> serverList = new ArrayList<>(); public static String roundRobin() { //獲取服務(wù)器數(shù)量 int serverCount = serverList.size(); //獲取當(dāng)前請(qǐng)求應(yīng)該轉(zhuǎn)發(fā)到哪臺(tái)服務(wù)器 int currentServerIndex = atomicInteger.incrementAndGet() % serverCount; //返回對(duì)應(yīng)的服務(wù)器地址 return serverList.get(currentServerIndex); }
二、加權(quán)輪詢法(Weight Round Robin)
加權(quán)輪詢法是在輪詢法的基礎(chǔ)上進(jìn)行改進(jìn),其思路是在服務(wù)器的選擇中,根據(jù)服務(wù)器的處理能力或負(fù)載情況分配不同的權(quán)重,以使處理能力較強(qiáng)或負(fù)載較輕的服務(wù)器獲得更多的請(qǐng)求。例如,若存在2臺(tái)服務(wù)器,其中第1臺(tái)服務(wù)器負(fù)載比較重,則應(yīng)當(dāng)將更多的請(qǐng)求分配給第2臺(tái)服務(wù)器。
舉個(gè)例子,如果服務(wù)器A的權(quán)重是2,服務(wù)器B的權(quán)重是1,那么在兩個(gè)請(qǐng)求中,有一個(gè)請(qǐng)求會(huì)被發(fā)送到服務(wù)器A,另一個(gè)請(qǐng)求將被發(fā)送到服務(wù)器B。
這種算法的優(yōu)點(diǎn)是可以根據(jù)服務(wù)器的實(shí)際負(fù)載情況來(lái)分配請(qǐng)求,但是還是存在服務(wù)器負(fù)載不均衡的問題,因?yàn)樗皇歉鶕?jù)權(quán)值進(jìn)行分配,并沒有考慮服務(wù)器的實(shí)際負(fù)載情況。
加權(quán)輪詢法按自己的思路寫一下如下示例:
//定義一個(gè)全局計(jì)數(shù)器,每次調(diào)用累加 private static AtomicInteger atomicInteger = new AtomicInteger(0); //定義服務(wù)器列表及服務(wù)器權(quán)重值 private static Map<String, Integer> serverMap = new ConcurrentHashMap<>(); //記錄服務(wù)器權(quán)重總和 private static int totalWeight = 0; public static String weightRoundRobin() { //獲取服務(wù)器數(shù)量 int serverCount = serverMap.size(); //如果沒有可用的服務(wù)器返回null if (serverCount == 0) { return null; } //在此處為避免多線程并發(fā)操作造成錯(cuò)誤,在方法內(nèi)部進(jìn)行鎖操作 synchronized (serverMap) { //計(jì)算服務(wù)器權(quán)重總和 for (Map.Entry<String, Integer> entry : serverMap.entrySet()) { totalWeight += entry.getValue(); } //獲取當(dāng)前請(qǐng)求應(yīng)該轉(zhuǎn)發(fā)到哪臺(tái)服務(wù)器 int currentServerIndex = atomicInteger.incrementAndGet() % totalWeight; //遍歷服務(wù)器列表,根據(jù)服務(wù)器權(quán)重值選擇對(duì)應(yīng)地址 for (Map.Entry<String, Integer> entry : serverMap.entrySet()) { String serverAddress = entry.getKey(); Integer weight = entry.getValue(); currentServerIndex -= weight; if (currentServerIndex < 0) { return serverAddress; } } } //默認(rèn)返回null return null; }
這是源碼中的一個(gè)小小的摘抄,供你觀賞一下:
public class WeightRoundRobinLoadBalancer implements LoadBalancer { private List<String> servers = new ArrayList<>(); private Map<String, Integer> weightMap = new HashMap<>(); private int currentWeightIndex = -1; public WeightRoundRobinLoadBalancer(Map<String, Integer> servers) { this.servers.addAll(servers.keySet()); for (String server : servers.keySet()) { int weight = servers.get(server); weightMap.put(server, weight); } } @Override public synchronized String chooseServer() { int weightSum = weightMap.values().stream().reduce(Integer::sum).orElse(0); while (true) { currentWeightIndex = (currentWeightIndex + 1) % servers.size(); String server = servers.get(currentWeightIndex); int weight = weightMap.get(server); if (weight >= weightSum) { return server; } weightSum -= weight; } } }
三、隨機(jī)法(Random)
隨機(jī)法是指將請(qǐng)求隨機(jī)分配至后端服務(wù)器的負(fù)載均衡算法。該算法實(shí)現(xiàn)簡(jiǎn)單,但分配效果不可控,難以保證后端服務(wù)器的負(fù)載均衡。因此,隨機(jī)法通常被用作測(cè)試或壓力測(cè)試等臨時(shí)場(chǎng)景下的負(fù)載均衡算法。
隨機(jī)法實(shí)現(xiàn)代碼如下:
// 1、思路參考:----------------------------------------------- //定義服務(wù)器列表 private static List<String> serverList = new ArrayList<>(); public static String random() { //獲取服務(wù)器數(shù)量 int serverCount = serverList.size(); //如果沒有可用的服務(wù)器返回null if (serverCount == 0) { return null; } //生成一個(gè)隨機(jī)數(shù) int randomIndex = new Random().nextInt(serverCount); //返回對(duì)應(yīng)的服務(wù)器地址 return serverList.get(randomIndex); } // 2、源碼參考:----------------------------------------------- public class RandomLoadBalancer implements LoadBalancer { private List<String> servers = new ArrayList<>(); public RandomLoadBalancer(List<String> servers) { this.servers = servers; } @Override public String chooseServer() { int randomIndex = ThreadLocalRandom.current().nextInt(servers.size()); return servers.get(randomIndex); } }
四、加權(quán)隨機(jī)法(Weight Random)
加權(quán)隨機(jī)法是在隨機(jī)法的基礎(chǔ)上進(jìn)行改進(jìn),其思路是在服務(wù)器的選擇中,根據(jù)服務(wù)器的處理能力或負(fù)載情況分配不同的權(quán)重,以使處理能力較強(qiáng)或負(fù)載較輕的服務(wù)器獲得更多的請(qǐng)求。
加權(quán)隨機(jī)法實(shí)現(xiàn)代碼如下:
// 1、思路參考:----------------------------------------------------------- //定義服務(wù)器列表及服務(wù)器權(quán)重值 private static Map<String, Integer> serverMap = new ConcurrentHashMap<>(); //記錄服務(wù)器權(quán)重總和 private static int totalWeight = 0; public static String weightRandom() { //獲取服務(wù)器數(shù)量 int serverCount = serverMap.size(); //如果沒有可用的服務(wù)器返回null if (serverCount == 0) { return null; } //在此處為避免多線程并發(fā)操作造成錯(cuò)誤,在方法內(nèi)部進(jìn)行鎖操作 synchronized (serverMap) { //計(jì)算服務(wù)器權(quán)重總和 for (Map.Entry<String, Integer> entry : serverMap.entrySet()) { totalWeight += entry.getValue(); } //生成一個(gè)隨機(jī)數(shù) int randomWeight = new Random().nextInt(totalWeight); //遍歷服務(wù)器列表,根據(jù)服務(wù)器權(quán)重值選擇對(duì)應(yīng)地址 for (Map.Entry<String, Integer> entry : serverMap.entrySet()) { String serverAddress = entry.getKey(); Integer weight = entry.getValue(); randomWeight -= weight; if (randomWeight < 0) { return serverAddress; } } } //默認(rèn)返回null return null; } // 2、源碼參考:----------------------------------------------------------- public class WeightRandomLoadBalancer implements LoadBalancer { private List<String> servers = new ArrayList<>(); private Map<String, Integer> weightMap = new HashMap<>(); public WeightRandomLoadBalancer(Map<String, Integer> servers) { this.servers.addAll(servers.keySet()); for (String server : servers.keySet()) { int weight = servers.get(server); weightMap.put(server, weight); } } @Override public String chooseServer() { int weightSum = weightMap.values().stream().reduce(Integer::sum).orElse(0); int randomWeight = ThreadLocalRandom.current().nextInt(weightSum) + 1; for (String server : servers) { int weight = weightMap.get(server); if (randomWeight <= weight) { return server; } randomWeight -= weight; } return null; } }
五、源地址哈希法(Hash)
源地址哈希法是一種基于請(qǐng)求源IP地址的負(fù)載均衡算法,其思路是將每個(gè)請(qǐng)求的源IP地址通過哈希函數(shù)計(jì)算出一個(gè)值,然后根據(jù)該值與可用服務(wù)器總數(shù)取模的結(jié)果來(lái)確定該請(qǐng)求應(yīng)當(dāng)轉(zhuǎn)發(fā)到哪臺(tái)服務(wù)器上。
換言之,源地址哈希算法就是使用客戶端 IP 地址作為哈希鍵。負(fù)載均衡器將哈希值映射到可用服務(wù)器中的一個(gè),然后將請(qǐng)求發(fā)送到這個(gè)服務(wù)器處理。如果客戶端 IP 地址發(fā)生改變(比如重啟后重新分配 IP 地址),那么將會(huì)被分配到其他服務(wù)器上。
這種算法的優(yōu)點(diǎn)是可以避免某些客戶端被重定向到不同的服務(wù)器,對(duì)于同一IP地址的請(qǐng)求,總是會(huì)被分配到同一臺(tái)服務(wù)器上,因此可以在一定程度上提高緩存命中率等性能指標(biāo),但是它也有一些缺點(diǎn)。例如,如果有很多請(qǐng)求來(lái)自相同的 IP 地址,那么可能會(huì)導(dǎo)致某個(gè)服務(wù)器負(fù)載過高。另外,由于服務(wù)器數(shù)量的變化,哈希值映射也會(huì)發(fā)生變化,這可能會(huì)導(dǎo)致緩存無(wú)效,并且需要重新分配所有請(qǐng)求。
源地址哈希法實(shí)現(xiàn)代碼示例如下:
// 1、思路參考:----------------------------------------------------------- //定義服務(wù)器列表 private static List<String> serverList = new ArrayList<>(); public static String hash(String clientIP) { //獲取服務(wù)器數(shù)量 int serverCount = serverList.size(); //如果沒有可用的服務(wù)器返回null if (serverCount == 0) { return null; } //將客戶端IP地址進(jìn)行哈希計(jì)算 int hashCode = clientIP.hashCode(); //根據(jù)哈希值計(jì)算需要轉(zhuǎn)發(fā)到哪臺(tái)服務(wù)器上 int serverIndex = hashCode % serverCount; //返回對(duì)應(yīng)的服務(wù)器地址 return serverList.get(serverIndex); } // 2、源碼參考:----------------------------------------------------------- public class HashLoadBalancer implements LoadBalancer { private List<String> servers = new ArrayList<>(); public HashLoadBalancer(List<String> servers) { this.servers = servers; } @Override public String chooseServer() { String clientIp = getClientIp(); int hashCode = Math.abs(clientIp.hashCode()); return servers.get(hashCode % servers.size()); } private String getClientIp() { // 獲取客戶端IP地址的代碼省略 return "1.1.1.1"; } }
六、最小連接數(shù)法(Least Connections)
最小連接數(shù)法是一種動(dòng)態(tài)調(diào)整的負(fù)載均衡算法,其思路是盡可能地將請(qǐng)求分配給當(dāng)前空閑連接數(shù)最少的后端服務(wù)器,以達(dá)到負(fù)載均衡的效果。在實(shí)現(xiàn)過程中,通常需要定期檢測(cè)各個(gè)服務(wù)器的連接數(shù)并進(jìn)行動(dòng)態(tài)調(diào)整。
最小連接數(shù)算法是根據(jù)當(dāng)前連接數(shù)來(lái)選擇一個(gè)可用服務(wù)器。負(fù)載均衡器會(huì)查詢可用服務(wù)器的連接數(shù),然后選擇一個(gè)連接數(shù)最小的服務(wù)器。這種算法保證了服務(wù)器不會(huì)被過度負(fù)載,并且還允許負(fù)載均衡器根據(jù)實(shí)際情況動(dòng)態(tài)分配請(qǐng)求。
需要注意的是,如果服務(wù)器掛掉了或者網(wǎng)絡(luò)鏈路中斷了,那么負(fù)載均衡器就需要重新計(jì)算服務(wù)器的連接數(shù),這將延長(zhǎng)響應(yīng)時(shí)間并且影響性能。
最小連接數(shù)法實(shí)現(xiàn)代碼示例如下:
// 1、思路參考:----------------------------------------------------------- //定義服務(wù)器列表 private static List<String> serverList = new ArrayList<>(); //記錄每個(gè)服務(wù)器的連接數(shù) private static Map<String, Integer> connectionsMap = new ConcurrentHashMap<>(); public static String leastConnections() { //獲取服務(wù)器數(shù)量 int serverCount = serverList.size(); //如果沒有可用的服務(wù)器返回null if (serverCount == 0) { return null; } //默認(rèn)選擇第一個(gè)服務(wù)器 String selectedServerAddress = serverList.get(0); //獲取第一個(gè)服務(wù)器的連接數(shù) int minConnections = connectionsMap.getOrDefault(selectedServerAddress, 0); //遍歷服務(wù)器列表,尋找連接數(shù)最少的服務(wù)器 for (int i = 1; i < serverCount; i++) { String serverAddress = serverList.get(i); int connections = connectionsMap.getOrDefault(serverAddress, 0); if (connections < minConnections) { selectedServerAddress = serverAddress; minConnections = connections; } } //返回連接數(shù)最少的服務(wù)器地址 return selectedServerAddress; } // 2、源碼參考:----------------------------------------------------------- public class LeastConnectionsLoadBalancer implements LoadBalancer { private List<String> servers = new ArrayList<>(); private Map<String, Integer> connectionsMap = new HashMap<>(); public LeastConnectionsLoadBalancer(List<String> servers) { this.servers = servers; for (String server : servers) { connectionsMap.put(server, 0); } } @Override public synchronized String chooseServer() { int minConnections = Integer.MAX_VALUE; String targetServer = null; for (String server : servers) { int connections = connectionsMap.get(server); if (connections < minConnections) { minConnections = connections; targetServer = server; } } connectionsMap.put(targetServer, connectionsMap.get(targetServer) + 1); return targetServer; } public void releaseConnection(String server) { connectionsMap.put(server, connectionsMap.get(server) - 1); } }
以上便是常見的Java負(fù)載均衡算法,這些算法都有其自身的優(yōu)缺點(diǎn)和適用場(chǎng)景。
七、小結(jié)一下
Java 架構(gòu)師面臨的挑戰(zhàn)越來(lái)越大,我們需要在不斷發(fā)展的技術(shù)中保持敏銳的觸覺,并掌握越來(lái)越廣泛的知識(shí)。而在當(dāng)前互聯(lián)網(wǎng)架構(gòu)中,負(fù)載均衡算法是一個(gè)至關(guān)重要的領(lǐng)域。它是實(shí)現(xiàn)服務(wù)的高可用性和可伸縮性的重要手段。因此,Java 架構(gòu)師必須深入學(xué)習(xí)各類負(fù)載均衡算法的實(shí)現(xiàn)方式,并且理解它們的優(yōu)劣之處,以便為公司設(shè)計(jì)出更好的網(wǎng)絡(luò)架構(gòu)。
如果你想成為一名 Java 架構(gòu)師或者網(wǎng)絡(luò)工程師,那么希望你多多了解底層知識(shí)對(duì)你將大有益處。讓我們一起努力吧!
以上就是使用Java實(shí)現(xiàn)6種常見負(fù)載均衡算法的詳細(xì)內(nèi)容,更多關(guān)于Java 負(fù)載均衡的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Java實(shí)現(xiàn)5種負(fù)載均衡算法(小結(jié))
- Java負(fù)載均衡算法實(shí)現(xiàn)之輪詢和加權(quán)輪詢
- java開發(fā)Dubbo負(fù)載均衡與集群容錯(cuò)示例詳解
- 使用Java實(shí)現(xiàn)5種負(fù)載均衡算法實(shí)例
- Java Grpc實(shí)例創(chuàng)建負(fù)載均衡詳解
- 詳解Java實(shí)現(xiàn)負(fù)載均衡的幾種算法代碼
- Java?Ribbon與openfeign區(qū)別和用法講解
- Java中的服務(wù)發(fā)現(xiàn)與負(fù)載均衡及Eureka與Ribbon的應(yīng)用小結(jié)
相關(guān)文章
解決Java?結(jié)構(gòu)化數(shù)據(jù)處理開源庫(kù)?SPL的問題
這篇文章主要介紹了Java?結(jié)構(gòu)化數(shù)據(jù)處理開源庫(kù)?SPL的問題,Scala提供了較豐富的結(jié)構(gòu)化數(shù)據(jù)計(jì)算函數(shù),但編譯型語(yǔ)言的特點(diǎn),也使它不能成為理想的結(jié)構(gòu)化數(shù)據(jù)計(jì)算類庫(kù),對(duì)此內(nèi)容感興趣的朋友一起看看吧2022-03-03Springboot啟動(dòng)報(bào)錯(cuò)時(shí)實(shí)現(xiàn)異常定位
這篇文章主要介紹了Springboot啟動(dòng)報(bào)錯(cuò)時(shí)實(shí)現(xiàn)異常定位,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06Spring boot項(xiàng)目redisTemplate實(shí)現(xiàn)輕量級(jí)消息隊(duì)列的方法
這篇文章主要給大家介紹了關(guān)于Spring boot項(xiàng)目redisTemplate實(shí)現(xiàn)輕量級(jí)消息隊(duì)列的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Spring boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04Spark Streaming編程初級(jí)實(shí)踐詳解
這篇文章主要為大家介紹了Spark Streaming編程初級(jí)實(shí)踐詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04Java中動(dòng)態(tài)地改變數(shù)組長(zhǎng)度及數(shù)組轉(zhuǎn)Map的代碼實(shí)例分享
這篇文章主要介紹了Java中動(dòng)態(tài)地改變數(shù)組長(zhǎng)度及數(shù)組轉(zhuǎn)map的代碼分享,其中轉(zhuǎn)Map利用到了java.util.Map接口,需要的朋友可以參考下2016-03-03