java分散式之負載均衡
在傳統的系統中,應用程式、資料伺服器、檔案伺服器等都在同一臺機器上部署。隨著網際網路的發展,系統的訪問量的增加,一臺機器根本滿足不了多使用者的高併發訪問,在
計算機領域,當單機效能達到瓶頸時,有兩種方式可以解決效能問題,一是堆硬體,進一步提升配置,二是分散式,水平擴充套件。所以,假如一臺機器不能
滿足高併發的請求訪問,那麼就兩臺,兩臺還滿足不了就增加到三臺,這種方式我們稱之為水平擴充套件,如何實現請求的平均分配便是負載均衡了。
負載均衡,英文名稱(Load Balance),簡稱 LB。指由多臺伺服器以對稱的方式組成一個伺服器集合,每臺伺服器都具有等價的地位,都可以單獨對外提供服務而無須其他服
務器的輔助。通過某種負載分擔技術,將外部發送來的請求均勻分配到對稱結構中的某一臺伺服器上,而接收到請求的伺服器獨立地迴應客戶的請求。負載均衡能夠
平均分配客戶請求到服 務器陣列,藉此提供快速獲取重要資料,解決大量併發訪問服務問題,這種叢集技術可以用最少的投資獲得接近於大型主機的效能。
那麼如何來實現把請求均勻地分配到不同的分散式伺服器上呢。這裡介紹幾種演算法:
首先我們先定義一個map的IP地址,指每臺伺服器
public class IpMap
2 {
3 // 待路由的Ip列表,Key代表Ip,Value代表該Ip的權重
4 public static HashMap<String, Integer> serverWeightMap =
5 new HashMap<String, Integer>();
6
7 static
8 {
9 serverWeightMap.put("192.168.1.100", 1);
10 serverWeightMap.put("192.168.1.101", 1);
11 // 權重為4
12 serverWeightMap.put("192.168.1.102", 4);
13 serverWeightMap.put("192.168.1.103", 1);
14 serverWeightMap.put("192.168.1.104", 1);
15 // 權重為3
16 serverWeightMap.put("192.168.1.105", 3);
17 serverWeightMap.put("192.168.1.106", 1);
18 // 權重為2
19 serverWeightMap.put("192.168.1.107", 2);
20 serverWeightMap.put("192.168.1.108", 1);
21 serverWeightMap.put("192.168.1.109", 1);
22 serverWeightMap.put("192.168.1.110", 1);
23 }
24 }
1、輪詢演算法
public class RoundRobin
2 {
3 private static Integer pos = 0;
4
5 public static String getServer()
6 {
7 // 重建一個Map,避免伺服器的上下線導致的併發問題
8 Map<String, Integer> serverMap =
9 new HashMap<String, Integer>();
10 serverMap.putAll(IpMap.serverWeightMap);
11
12 // 取得Ip地址List
13 Set<String> keySet = serverMap.keySet();
14 ArrayList<String> keyList = new ArrayList<String>();
15 keyList.addAll(keySet);
16
17 String server = null;
18 synchronized (pos)
19 {
20 if (pos > keySet.size())
21 pos = 0;
22 server = keyList.get(pos);
23 pos ++;
24 }
25
26 return server;
27 }
28 }
由於serverWeightMap中的伺服器可能會出現新增、下線或者宕機,為了避免可能出現的併發問題(?),方法內部要新建區域性變數serverMap,現將
serverMap中的內容複製到執行緒本地,以避免被多 個執行緒修改。這樣可能會引入新的問題,複製以後serverWeightMap的修改無法反映給serverMap,也就是
說這一輪選擇伺服器的過程中, 新增伺服器或者下線伺服器,負載均衡演算法將無法獲知。新增無所謂,如果有伺服器下線或者宕機,那麼可能會訪問到不存在的
地址。因此,服務呼叫端需要有相應的容錯處理,比如重新發起一次server選擇並呼叫。對於當前輪詢的位置變數pos,為了保證伺服器選擇的順序性,需要在操
作時對其加鎖,使得同一時刻只能有一個執行緒可以修改pos的值。
2、隨機法
public class Random
2 {
3 public static String getServer()
4 {
5 // 重建一個Map,避免伺服器的上下線導致的併發問題
6 Map<String, Integer> serverMap =
7 new HashMap<String, Integer>();
8 serverMap.putAll(IpMap.serverWeightMap);
9
10 // 取得Ip地址List
11 Set<String> keySet = serverMap.keySet();
12 ArrayList<String> keyList = new ArrayList<String>();
13 keyList.addAll(keySet);
14
15 java.util.Random random = new java.util.Random();
16 int randomPos = random.nextInt(keyList.size());
17
18 return keyList.get(randomPos);
19 }
20 }
在選取server的時候,通過Random的nextInt方法取0~keyList.size()區間的一個隨機值,然後獲得伺服器並返回。
3、雜湊地址法
public class Hash
2 {
3 public static String getServer()
4 {
5 // 重建一個Map,避免伺服器的上下線導致的併發問題
6 Map<String, Integer> serverMap =
7 new HashMap<String, Integer>();
8 serverMap.putAll(IpMap.serverWeightMap);
9
10 // 取得Ip地址List
11 Set<String> keySet = serverMap.keySet();
12 ArrayList<String> keyList = new ArrayList<String>();
13 keyList.addAll(keySet);
14
15 // 在Web應用中可通過HttpServlet的getRemoteIp方法獲取
16 String remoteIp = "127.0.0.1";
17 int hashCode = remoteIp.hashCode();
18 int serverListSize = keyList.size();
19 int serverPos = hashCode % serverListSize;
20
21 return keyList.get(serverPos);
22 }
23 }
雜湊演算法就是通過客戶端的IP地址,取得它的hash值,然後對伺服器列表個數取模,來取得相應的伺服器。
總結:
輪詢演算法:優點是基本能夠實現請求的均衡分配;缺點是為了使得服務分配的順序性,必須得引入悲觀鎖(synchronized),影響併發訪問的效率。
隨機演算法:基於概率統計原理,吞吐量越大,隨機發越能接近輪詢法的平均效果。
hash演算法:保證了相同客戶端IP地址將會被雜湊到同一臺後端伺服器,直到後端伺服器列表變更。根據此特性可以在服務消費者與服務提供者之間建立有狀態的
session會話。缺點是如果伺服器發生上下線或者宕機的情況,會造成不能正確路由到以前所路由到的伺服器上,如果是session則取不到session,如果是快取則
可能引發"雪崩"。
4、加權輪詢法或加權隨機法
另外:由於不同伺服器的配置不同,抗壓能力也不盡相同,那麼為了給那些配置好的多些負載,配置低的伺服器少些負載,就引入了權重的概念。
public class WeightRoundRobin
2 {
3 private static Integer pos;
4
5 public static String getServer()
6 {
7 // 重建一個Map,避免伺服器的上下線導致的併發問題
8 Map<String, Integer> serverMap =
9 new HashMap<String, Integer>();
10 serverMap.putAll(IpMap.serverWeightMap);
11
12 // 取得Ip地址List
13 Set<String> keySet = serverMap.keySet();
14 Iterator<String> iterator = keySet.iterator();
15
16 List<String> serverList = new ArrayList<String>();
17 while (iterator.hasNext())
18 {
19 String server = iterator.next();
20 int weight = serverMap.get(server);
21 for (int i = 0; i < weight; i++)
22 serverList.add(server);
23 }
24
25 String server = null;
26 synchronized (pos)
27 {
28 if (pos > keySet.size())
29 pos = 0;
30 server = serverList.get(pos);
31 pos ++;
32 }
33
34 return server;
35 }
36 }
關鍵是在獲得伺服器之前加入了下面的權重程式碼,如果權重大,在serverList裡面的該服務就多,就能多分得幾次請求。
while (iterator.hasNext())
18 {
19 String server = iterator.next();
20 int weight = serverMap.get(server);
21 for (int i = 0; i < weight; i++)
22 serverList.add(server);
23 }