五種常用的負載均衡算法
大家好,我是猿java。
我們介紹過,網(wǎng)關(guān)最重要的功能之一就是負載均衡,那么,什么是負載均衡?負載均衡有哪些方式?今天我們就來聊一聊。
一、定義
負載均衡(Load Balancing)是一種計算機網(wǎng)絡(luò)和服務(wù)器管理技術(shù),旨在分配網(wǎng)絡(luò)流量、請求或工作負載到多個服務(wù)器或資源,以確保這些服務(wù)器能夠高效、均勻地處理負載,并且能夠提供更高的性能、可用性和可擴展性。
二、負載均衡算法
1.Round Robin-輪詢
輪詢,顧名思義,把請求按順序分配給每個服務(wù)器,然后重復(fù)執(zhí)行這個順序,進行請求分配。如下圖:
如上圖,有3臺服務(wù)器,分別為服務(wù)器A、服務(wù)器B和服務(wù)器C,當(dāng)客戶端有請求過來時,請求會按照 A->B->C->A->B->C->… 這種輪詢的順序分配給各個服務(wù)器。
(1) 原理:
- 服務(wù)器列表:維護一個服務(wù)器列表,有服務(wù)器加入/剔除時,相應(yīng)的更新服務(wù)器列表;
- 服務(wù)器游標:記錄需要處理下一個請求的服務(wù)器;
- 請求分發(fā):新請求到達,選擇當(dāng)前服務(wù)器來處理該請求,然后服務(wù)器游標+1;
- 循環(huán):不斷重復(fù)步驟3,以確保每個服務(wù)器都有機會處理請求;
(2) 算法實現(xiàn)
方法1:
輪詢算法的實現(xiàn)非常簡單,可以定義一個服務(wù)器的列表和當(dāng)前服務(wù)器指針,如下偽代碼:
# 服務(wù)器列表
servers = ["ServerA", "ServerB", "ServerC"]
# 當(dāng)前服務(wù)器
current_server = 0
# 輪詢算法
if(req):
# 選擇當(dāng)前服務(wù)器來處理請求
process_request(servers[current_server])
# 將當(dāng)前服務(wù)器移到服務(wù)器列表的末尾
if current_server == length(servers):
current_server = 0
else:
# 指針+1
current_server += 1
當(dāng)客戶端有新的請求到達時,負載均衡器會選擇服務(wù)器指針(current_server)指向的服務(wù)器來處理請求,然后將當(dāng)前服務(wù)器指針移到下一個服務(wù)器(current_server += 1), 如果 current_server=服務(wù)器總數(shù),則把current_server設(shè)置為0,進行下一場輪詢。
方法2: 循環(huán)列表
循環(huán)列表是一個環(huán)形數(shù)據(jù)結(jié)構(gòu),用于按照順序循環(huán)遍歷服務(wù)器列表。當(dāng)指針指向列表的末尾時,指針會回到列表的開頭,從而實現(xiàn)循環(huán)。如下偽代碼:
servers = ["Server1", "Server2", "Server3"] # 服務(wù)器列表
current_index = 0 # 當(dāng)前服務(wù)器的索引
def get_next_server(self):
if not self.servers:
return None
# 獲取當(dāng)前服務(wù)器
current_server = self.servers[self.current_index]
# 更新索引,移到下一個服務(wù)器
self.current_index = (self.current_index + 1) % len(self.servers)
return current_server
# 創(chuàng)建一個包含服務(wù)器的列表
servers_list = ["ServerA", "ServerB", "ServerC"]
# 模擬請求的處理過程
if(req): # 假設(shè)有5個請
next_server = get_next_server()
if next_server is not None:
process_request(next_server)
else:
print("No available servers.")
(3) 優(yōu)缺點
優(yōu)點:簡單,實現(xiàn)成本低;
缺點:
- 無法根據(jù)服務(wù)器的負載情況來分配請求,當(dāng)服務(wù)器的負載不均衡時,輪詢算法無法自動調(diào)整。
- 當(dāng)服務(wù)器down機了,輪詢算法無法自動剔除該服務(wù)器,導(dǎo)致請求會被轉(zhuǎn)發(fā)到down機的服務(wù)器上。
servers = ["Server1", "Server2", "Server3"] # 服務(wù)器列表
current_index = 0 # 當(dāng)前服務(wù)器的索引
def get_next_server(self):
if not self.servers:
return None
# 獲取當(dāng)前服務(wù)器
current_server = self.servers[self.current_index]
# 更新索引,移到下一個服務(wù)器
self.current_index = (self.current_index + 1) % len(self.servers)
return current_server
# 創(chuàng)建一個包含服務(wù)器的列表
servers_list = ["ServerA", "ServerB", "ServerC"]
# 模擬請求的處理過程
if(req): # 假設(shè)有5個請
next_server = get_next_server()
if next_server is not None:
process_request(next_server)
else:
print("No available servers.")
(4) 適用場景
對服務(wù)器沒有什么特別的要求,就可以采用輪詢算法,比如:Nginx 默認適用的就是輪詢算法。
2.Weighted Round Robin - 加權(quán)輪詢
加權(quán)輪詢算法是輪詢算法的一種改進,只不過在負載時會根據(jù)服務(wù)器的權(quán)重來分配請求,權(quán)重越大,分配的請求就會越多。如下圖:
(1) 算法實現(xiàn)
實現(xiàn)算法和輪詢很類似,只不過會根據(jù)權(quán)重在列表中放置不同比例的服務(wù)器,同時定義一個服務(wù)器的列表和當(dāng)前服務(wù)器指針,如下偽代碼:
# 服務(wù)器列表
servers = ["ServerA", "ServerA", "ServerA", "ServerB","ServerB", "ServerC"]
# 當(dāng)前服務(wù)器
current_server = 0
# 輪詢算法
if(req):
# 選擇當(dāng)前服務(wù)器來處理請求
process_request(servers[current_server])
# 將當(dāng)前服務(wù)器移到服務(wù)器列表的末尾
if current_server == length(servers):
current_server = 0
else:
# 指針+1
current_server += 1
當(dāng)客戶端有新的請求到達時,負載均衡器會選擇服務(wù)器指針(current_server)指向的服務(wù)器來處理請求,然后將當(dāng)前服務(wù)器指針移到下一個服務(wù)器(current_server += 1), 如果 current_server=服務(wù)器總數(shù),則把current_server設(shè)置為0,進行下一場輪詢。
(2) 優(yōu)缺點
優(yōu)點:可以人為配置權(quán)重,為處理能力強的服務(wù)器配置高的權(quán)重,處理能力弱的配置低的權(quán)重,從而實現(xiàn)負載均衡。
缺點:無法應(yīng)對服務(wù)器動態(tài)變化的情況,比如:服務(wù)器down機了,無法自動剔除該服務(wù)器,導(dǎo)致請求會被轉(zhuǎn)發(fā)到down機的服務(wù)器上。
(3) 適用場景
服務(wù)器的處理能力不一致,可以采用加權(quán)輪詢算法。
比如:有3臺服務(wù)器,服務(wù)器A(4C8G,4個CPU,8G內(nèi)存),服務(wù)器B(2C4G,2個CPU,4G內(nèi)存),服務(wù)器C(1C2G,1個CPU,2G內(nèi)存),那么可以配置服務(wù)器A的權(quán)重為4,服務(wù)器B的權(quán)重為2,服務(wù)器C的權(quán)重為1。
3.Least Connections - 最小連接數(shù)
最小連接數(shù),是指把請求分配給當(dāng)前連接數(shù)最少的服務(wù)器,以確保負載更均勻。如下圖:
上圖中有 3臺服務(wù)器,服務(wù)器A(連接數(shù)10)、服務(wù)器B(連接數(shù)100)和服務(wù)器C(連接數(shù)1000),連接數(shù)最少的服務(wù)器A分配的Req比其他服務(wù)器多。
(1) 原理
- 維護一個所有服務(wù)器和連接數(shù)的字典(Map);
- 當(dāng)新的請求到達時,負載均衡器會檢查服務(wù)器列表中當(dāng)前連接數(shù)最少的服務(wù)器;
- 請求將被分配給具有最少連接數(shù)的服務(wù)器,處理請求后該服務(wù)器的連接數(shù)+1;
- 如果有多臺服務(wù)器具有相同的最小連接數(shù),算法可以使用其他標準來選擇其中一臺,如加權(quán)等。
(2) 算法實現(xiàn)
如下偽代碼:
# 創(chuàng)建一個包含服務(wù)器及其連接數(shù)的字典
servers = {"Server A": 5, "Server B": 3, "Server C": 4}
def get_server_with_least_connections():
# 找到當(dāng)前連接數(shù)最少的服務(wù)器
min_connections = min(servers.values())
# 找到具有最小連接數(shù)的服務(wù)器
for server, connections in servers.items():
if connections == min_connections:
return server
# 選擇連接數(shù)最少的服務(wù)器
def assign_request(self):
# 獲取具有最小連接數(shù)的服務(wù)器
server = get_server_with_least_connections()
if server is not None:
# 模擬分配請求給服務(wù)器,增加連接數(shù)
self.servers[server] += 1
return server
else:
return "No available servers."
# 模擬請求的處理過程
if req: # 假設(shè)有請求
assigned_server = load_balancer.assign_request()
(3) 優(yōu)缺點
優(yōu)點:
- 動態(tài)負載均衡:它根據(jù)服務(wù)器的當(dāng)前負載情況來做出決策,這使得它能夠有效地分配請求給當(dāng)前連接數(shù)最少的服務(wù)器,從而確保了服務(wù)器資源的最佳利用。
- 適應(yīng)性強:這個算法適用于服務(wù)器性能不均勻的情況,因為它關(guān)注的是連接數(shù),而不是服務(wù)器的硬件配置或性能評估。
- 避免過載:通過將新請求分配給連接數(shù)最少的服務(wù)器,”最小連接數(shù)”算法有助于防止某些服務(wù)器被過度加載,從而提高了系統(tǒng)的穩(wěn)定性和性能。
- 自動恢復(fù):如果某臺服務(wù)器由于故障或重啟而導(dǎo)致連接數(shù)清零,該算法會自動開始將新請求分配給該服務(wù)器,以實現(xiàn)自動恢復(fù)。
缺點:
- 連接數(shù)不一定代表負載:”最小連接數(shù)”算法假設(shè)連接數(shù)與服務(wù)器的負載成正比,但這并不總是準確。有時候,某臺服務(wù)器的連接數(shù)可能很高,但仍然能夠處理更多的請求,而另一臺連接數(shù)較低的服務(wù)器可能已經(jīng)達到了其性能極限。
- 不適用于長連接:如果服務(wù)器上有大量長期活躍的連接,例如WebSocket連接,該算法可能不太適用,因為長連接不同于短暫的HTTP請求,連接數(shù)的統(tǒng)計可能會產(chǎn)生誤導(dǎo)。
- 無法解決服務(wù)器性能差異:雖然”最小連接數(shù)”算法可以平衡連接數(shù),但它無法解決服務(wù)器硬件性能差異的問題。在這種情況下,可能需要其他負載均衡算法,如加權(quán)輪詢,來更好地適應(yīng)性能差異。
(4) 適用場景
通過服務(wù)器連接數(shù)來做負載均衡的場景。到目前為止,還沒有遇到生產(chǎn)上使用這種算法的場景。
4.IP/URL Hash - IP/URL 散列
IP/URL 散列算法是一種根據(jù)客戶端 IP 地址或 URL 來分配請求的負載均衡算法,這樣相同的IP或者URL就會負載到相同的服務(wù)器上。
(1) 原理
- 將客戶端 IP 地址或 URL 散列到服務(wù)器列表中,
- 然后將請求分配給散列值對應(yīng)的服務(wù)器。
如下圖:有3臺服務(wù)器,分別為服務(wù)器A、服務(wù)器B和服務(wù)器C,當(dāng)相同IP的客戶端請求會被負載到形同的服務(wù)器列中。
(2) 優(yōu)缺點
優(yōu)點:
- 穩(wěn)定性:IP/URL Hash 算法可以確保相同的客戶端請求總是被分發(fā)到相同的服務(wù)器上。這可以提高應(yīng)用程序的穩(wěn)定性,因為客戶端的會話數(shù)據(jù)在同一服務(wù)器上保持一致。
- 適用于會話保持:當(dāng)應(yīng)用程序需要在多次請求之間保持會話狀態(tài)時,IP/URL Hash 算法非常有用??蛻舳嗽谝淮握埱笾羞x擇的服務(wù)器會在后續(xù)請求中保持一致,確保會話數(shù)據(jù)不會丟失。
- 負載均衡:IP/URL Hash 算法可以將特定的客戶端請求均勻地分配到多個服務(wù)器上,從而實現(xiàn)基本的負載均衡,避免了某些服務(wù)器被過度請求。
缺點:
- 不適用于動態(tài)環(huán)境:IP/URL Hash 算法基于客戶端的 IP 地址或 URL,一旦客戶端 IP 或請求的 URL 發(fā)生變化,請求可能會被分配到不同的服務(wù)器上,導(dǎo)致會話數(shù)據(jù)丟失或不一致。
- 不考慮服務(wù)器負載:IP/URL Hash 算法不考慮服務(wù)器的當(dāng)前負載情況。如果某個服務(wù)器的負載過高,IP/URL Hash 無法動態(tài)地將請求分發(fā)到負載較低的服務(wù)器上。
(3) 適用場景
靜態(tài)環(huán)境:在靜態(tài)環(huán)境中,即客戶端的 IP 地址或請求的 URL 不經(jīng)常變化的情況下,IP/URL Hash 算法可以提供穩(wěn)定的負載均衡。
少數(shù)服務(wù)器的負載均衡:當(dāng)服務(wù)器數(shù)量相對較少且不太容易動態(tài)擴展時,IP/URL Hash 算法可以用于基本的負載均衡。
5.Least Response Time - 最短響應(yīng)時間
最短響應(yīng)時間就是指:處理請求的響應(yīng)時間最少的服務(wù)器,獲取的請求就越多。直白講就是隨速度快,隨就干的多。如下圖:
(1) 適用場景
負載均衡的所有服務(wù)器,處理能力相差比較大。比如:有3臺服務(wù)器,服務(wù)器A(4C8G,4個CPU,8G內(nèi)存),服務(wù)器B(2C4G,2個CPU,4G內(nèi)存),服務(wù)器C(1C2G,1個CPU,2G內(nèi)存), 那么就可以采用這種算法,這樣可以根據(jù)服務(wù)器的處理來實現(xiàn)動態(tài)負載。
(2) 優(yōu)缺點
優(yōu)點:可以充分發(fā)揮各個服務(wù)器的性能,提高服務(wù)器的利用率。
缺點:饑餓問題。比如,服務(wù)器A的性能最好,處理速度最快,那么所有的請求都會被分配到服務(wù)器A,這樣服務(wù)器B和服務(wù)器C就會一直處于饑餓狀態(tài),無法處理請求。這樣也就會產(chǎn)生不公平。
(3) 算法實現(xiàn)
如下偽代碼:記錄每臺服務(wù)器以及響應(yīng)時間,然后找到響應(yīng)時間最短的服務(wù)器,將請求分配到該服務(wù)器上。
# 服務(wù)器列表,每個服務(wù)器表示為一個字典,包含服務(wù)器的唯一標識符和響應(yīng)時間
servers = [
{"id": "serverA", "response_time": 10},
{"id": "serverB", "response_time": 30},
{"id": "serverC", "response_time": 100},
# 添加更多服務(wù)器
]
# 找到響應(yīng)時間最短的服務(wù)器
def find_least_response_time_server(servers):
# 初始選擇第一個服務(wù)器為最短響應(yīng)時間服務(wù)器
least_response_time_server = servers[0]
# 遍歷服務(wù)器列表,找到最短響應(yīng)時間的服務(wù)器
for server in servers:
if server["response_time"] < least_response_time_server["response_time"]:
least_response_time_server = server
return least_response_time_server
# 客戶端請求到來時,選擇最短響應(yīng)時間的服務(wù)器
def handle_client_request():
least_response_time_server = find_least_response_time_server(servers)
if req:
least_response_time_server.handle_client_request()
需要說明的是:這只是一個簡單的示例,實際的負載均衡系統(tǒng)可能需要更復(fù)雜的邏輯,包括定期更新服務(wù)器的響應(yīng)時間、處理服務(wù)器故障等。此外,要將這種算法應(yīng)用于實際生產(chǎn)環(huán)境,可能需要使用專門的負載均衡軟件或硬件,這些工具可以自動管理服務(wù)器并提供更多功能。
(4) 適用場景
交通控制系統(tǒng):在城市交通控制系統(tǒng)中,需要及時響應(yīng)交通信號、路況和車輛檢測等信息。最短響應(yīng)時間算法可以幫助確保交通信號及時適應(yīng)交通流量的變化。
三、總結(jié)
本文分析了五種常見的負載均衡算法,算法的實現(xiàn)都比較簡單,在實際的生產(chǎn)環(huán)境中,我們可以根據(jù)自己的業(yè)務(wù)場景來選擇合適的負載均衡算法。
另外,除了上面 5種算法外,還有一種其他的負載均衡算法,比如:
- 一致性哈希:Consistent Hashing,可以參考文章:hash & 一致性hash,如何選擇?
- 加權(quán)最少連接:Weighted Least Connections,在Weighted Least Connections基礎(chǔ)上再加權(quán)重。
在實際生產(chǎn)中,我們可能并不需要自己去實現(xiàn)這些算法,而會選擇使用一些現(xiàn)有的框架,比如:nginx、lvs、haproxy等, 但是萬變不離其宗,了解這些負載均衡算法可以幫組我們更好的去理解框架。