1. 程式人生 > 實用技巧 >常見負載均衡演算法

常見負載均衡演算法

一、概要

隨著系統日益龐大、邏輯業務越來越複雜,系統架構由原來的單一系統到垂直系統,發展到現在的分散式系統。分散式系統中,可以做到公共業務模組的高可用,高容錯性,高擴充套件性,然而,當系統越來越複雜時,需要考慮的東西自然也越來越多,要求也越來越高,比如服務路由、負載均衡等。此文將針對負載均衡演算法進行講解,不涉及具體的實現。

二、負載均衡演算法

在分散式系統中,多臺伺服器同時提供一個服務,並統一到服務配置中心進行管理,如圖1-1。消費者通過查詢服務配置中心,獲取到服務到地址列表,需要選取其中一臺來發起RPC遠端呼叫。如何選擇,則取決於具體的負載均衡演算法,對應於不同的場景,選擇的負載均衡演算法也不盡相同。負載均衡演算法的種類有很多種,常見的負載均衡演算法包括輪詢法、隨機法、源地址雜湊法、加權輪詢法、加權隨機法、最小連線法等,應根據具體的使用場景選取對應的演算法。

圖1-1

1、輪詢(Round Robin)法

輪詢很容易實現,將請求按順序輪流分配到後臺伺服器上,均衡的對待每一臺伺服器,而不關心伺服器實際的連線數和當前的系統負載。

這裡通過例項化一個serviceWeightMap的Map變數來伺服器地址和權重的對映,以此來模擬輪詢演算法的實現,其中設定的權重值在以後的加權演算法中會使用到,這裡先不做過多介紹,該變數初始化如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 privatestaticMap<String, Integer> serviceWeightMap =newHashMap<String, Integer>();
static{ serviceWeightMap.put("192.168.1.100",1); serviceWeightMap.put("192.168.1.101",1);<br>    //權重為4 serviceWeightMap.put("192.168.1.102",4); serviceWeightMap.put("192.168.1.103",1); serviceWeightMap.put("192.168.1.104",1);<br>//權重為3 serviceWeightMap.put("192.168.1.105",3); serviceWeightMap.put(
"192.168.1.106",1);<br>//權重為2 serviceWeightMap.put("192.168.1.107",2); serviceWeightMap.put("192.168.1.108",1); serviceWeightMap.put("192.168.1.109",1); serviceWeightMap.put("192.168.1.110",1); }

通過該地址列表,實現的輪詢演算法的部分關鍵程式碼如下

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 privatestaticInteger pos =0; publicstaticString testRoundRobin() { // 重新建立一個map,避免出現由於伺服器上線和下線導致的併發問題 Map<String, Integer> serverMap =newHashMap<String, Integer>(); serverMap.putAll(serviceWeightMap); //取得IP地址list Set<String> keySet = serverMap.keySet(); ArrayList<String> keyList =newArrayList<String>(); keyList.addAll(keySet); String server =null; synchronized(pos) { if(pos > keySet.size()) { pos =0; } server = keyList.get(pos); pos++; } returnserver; }

由於serviceWeightMap中的地址列表是動態的,隨時可能由機器上線、下線或者宕機,因此,為了避免可能出現的併發問題,比如陣列越界,通過在方法內新建區域性變數serverMap,先將域變數拷貝到執行緒本地,避免被其他執行緒修改。這樣可能會引入新的問題,當被拷貝之後,serviceWeightMap的修改將無法被serverMap感知,也就是說,在這一輪的選擇伺服器中,新增伺服器或者下線伺服器,負載均衡演算法中將無法獲知。新增比較好處理,而當伺服器下線或者宕機時,服務消費者將有可能訪問不到不存在的地址。因此,在服務消費者服務端需要考慮該問題,並且進行相應的容錯處理,比如重新發起一次呼叫。

對於當前輪詢的位置變數pos,為了保證伺服器選擇的順序性,需要對其在操作時加上synchronized鎖,使得同一時刻只有一個執行緒能夠修改pos的值,否則當pos變數被併發修改,將無法保證伺服器選擇的順序性,甚至有可能導致keyList陣列越界。

使用輪詢策略的目的是,希望做到請求轉移的絕對均衡,但付出的代價效能也是相當大的。為了保證pos變數的併發互斥,引入了重量級悲觀鎖synchronized,將會導致該輪詢程式碼的併發吞吐量明顯下降。

2、隨機法

通過系統隨機函式,根據後臺伺服器列表的大小值來隨機選取其中一臺進行訪問。由概率概率統計理論可以得知,隨著呼叫量的增大,其實際效果越來越接近於平均分配流量到後臺的每一臺伺服器,也就是輪詢法的效果。

隨機演算法的部分關鍵程式碼如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 publicstaticString testRandom() { // 重新建立一個map,避免出現由於伺服器上線和下線導致的併發問題 Map<String, Integer> serverMap =newHashMap<String, Integer>(); serverMap.putAll(serviceWeightMap); //取得IP地址list Set<String> keySet = serverMap.keySet(); ArrayList<String> keyList =newArrayList<String>(); keyList.addAll(keySet); Random random =newRandom(); intrandomPos = random.nextInt(keyList.size()); String server = keyList.get(randomPos); returnserver; }

跟前面類似,為了避免併發的問題,需要將serviceWeightMap拷貝到serverMap中。通過Random的nextInt函式,取到0~keyList.size之間的隨機值, 從而從伺服器列表中隨機取到一臺伺服器的地址,進行返回。根據概率統計理論,吞吐量越大,隨機演算法的效果越接近於輪詢演算法的效果。

3、源地址雜湊法

源地址雜湊法的思想是根據服務消費者請求客戶端的IP地址,通過雜湊函式計算得到一個雜湊值,將此雜湊值和伺服器列表的大小進行取模運算,得到的結果便是要訪問的伺服器地址的序號。採用源地址雜湊法進行負載均衡,相同的IP客戶端,如果伺服器列表不變,將對映到同一個後臺伺服器進行訪問。

源地址雜湊法部分關鍵程式碼如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 publicstaticString testConsumerHash(String remoteIp) { // 重新建立一個map,避免出現由於伺服器上線和下線導致的併發問題 Map<String, Integer> serverMap =newHashMap<String, Integer>(); serverMap.putAll(serviceWeightMap); //取得IP地址list Set<String> keySet = serverMap.keySet(); ArrayList<String> keyList =newArrayList<String>(); keyList.addAll(keySet); inthashCode = remoteIp.hashCode(); intpos = hashCode % keyList.size(); returnkeyList.get(pos); }

4、加權輪詢(Weight Round Robin)法

不同的後臺伺服器可能機器的配置和當前系統的負載並不相同,因此它們的抗壓能力也不一樣。跟配置高、負載低的機器分配更高的權重,使其能處理更多的請求,而配置低、負載高的機器,則給其分配較低的權重,降低其系統負載,加權輪詢很好的處理了這一問題,並將請求按照順序且根據權重分配給後端。

加權輪詢法部分關鍵程式碼如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 publicstaticString testWeightRoundRobin() { // 重新建立一個map,避免出現由於伺服器上線和下線導致的併發問題 Map<String, Integer> serverMap =newHashMap<String, Integer>(); serverMap.putAll(serviceWeightMap); //取得IP地址list Set<String> keySet = serverMap.keySet(); Iterator<String> it = keySet.iterator(); List<String> serverList =newArrayList<String>(); while(it.hasNext()) { String server = it.next(); Integer weight = serverMap.get(server); for(inti=0; i<weight; i++) { serverList.add(server); } } String server =null; synchronized (pos) { if(pos > serverList.size()) { pos = 0; } server = serverList.get(pos); pos++; } returnserver; }

與輪詢演算法類似,只是在獲取伺服器地址之前增加了一段權重計算程式碼,根據權重的大小,將地址重複增加到伺服器地址列表中,權重越大,該伺服器每輪所獲得的請求數量越多。

5、加權隨機(Weight Random)法

加權隨機法跟加權輪詢法類似,根據後臺伺服器不同的配置和負載情況,配置不同的權重。不同的是,它是按照權重來隨機選取伺服器的,而非順序。

部分關鍵程式碼如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 publicstaticString testWeightRandom() { // 重新建立一個map,避免出現由於伺服器上線和下線導致的併發問題 Map<String, Integer> serverMap =newHashMap<String, Integer>(); serverMap.putAll(serviceWeightMap); //取得IP地址list Set<String> keySet = serverMap.keySet(); List<String> serverList =newArrayList<String>(); Iterator<String> it = keySet.iterator(); while(it.hasNext()) { String server = it.next(); Integer weight = serverMap.get(server); for(inti=0; i<weight; i++) { serverList.add(server); } } Random random =newRandom(); intrandomPos = random.nextInt(serverList.size()); String server = serverList.get(randomPos); returnserver; }

6、最小連線數法

前面我們費盡心思來實現服務消費者請求次數分配的均衡,我們知道這樣做是沒錯的,可以為後端的多臺伺服器平均分配工作量,最大程度地提高伺服器的利用率,但是,實際上,請求次數的均衡並不代表負載的均衡。因此我們需要介紹最小連線數法,最小連線數法比較靈活和智慧,由於後臺伺服器的配置不盡相同,對請求的處理有快有慢,它正是根據後端伺服器當前的連線情況,動態的選取其中當前積壓連線數最少的一臺伺服器來處理當前請求,儘可能的提高後臺伺服器利用率,將負載合理的分流到每一臺伺服器。