首先給你們介紹下什麼是負載均衡(來自百科)java
負載均衡 創建在現有網絡結構之上,它提供了一種廉價有效透明的方法擴展 網絡設備和 服務器的帶寬、增長 吞吐量、增強網絡數據處理能力、提升網絡的靈活性和可用性。算法
負載均衡,英文名稱爲Load Balance,其意思就是分攤到多個操做單元上進行執行,例如Web 服務器、 FTP服務器、 企業關鍵應用服務器和其它關鍵任務服務器等,從而共同完成工做任務。後端
本文講述的是"將外部發送來的請求均勻分配到對稱結構中的某一臺服務器上"的各類算法,並以Java代碼演示每種算法的具體實現,OK,下面進入正題,在進入正題前,先寫一個類來模擬Ip列表:數組
import java.util.HashMap; /** * @author ashang.peng@aliyun.com * @date 二月 07, 2017 */ public class IpMap { // 待路由的Ip列表,Key表明Ip,Value表明該Ip的權重 public static HashMap<String, Integer> serverWeightMap = new HashMap<String, Integer>(); static { serverWeightMap.put("192.168.1.100", 1); serverWeightMap.put("192.168.1.101", 1); // 權重爲4 serverWeightMap.put("192.168.1.102", 4); serverWeightMap.put("192.168.1.103", 1); serverWeightMap.put("192.168.1.104", 1); // 權重爲3 serverWeightMap.put("192.168.1.105", 3); serverWeightMap.put("192.168.1.106", 1); // 權重爲2 serverWeightMap.put("192.168.1.107", 2); serverWeightMap.put("192.168.1.108", 1); serverWeightMap.put("192.168.1.109", 1); serverWeightMap.put("192.168.1.110", 1); } }
Java緩存
輪詢調度算法的原理是每一次把來自用戶的請求輪流分配給內部中的服務器,從1開始,直到N(內部服務器個數),而後從新開始循環。算法的優勢是其簡潔性,它無需記錄當前全部鏈接的狀態,因此它是一種無狀態調度。服務器
其代碼實現大體以下:網絡
import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * @author ashang.peng@aliyun.com * @date 二月 07, 2017 */ class RoundRobin { private static Integer pos = 0; public static String getServer() { // 重建一個Map,避免服務器的上下線致使的併發問題 Map<String, Integer> serverMap = new HashMap<String, Integer>(); serverMap.putAll(IpMap.serverWeightMap); // 取得Ip地址List Set keySet = serverMap.keySet(); ArrayList keyList = new ArrayList(); keyList.addAll(keySet); String server = null; synchronized (pos) { // if (pos > keySet.size()) 謝謝@WO還在這兒的提醒, // 應該為**-1否則確實會出現IndexOutOfBoundsException if (pos > keySet.size()-1) pos = 0; server = keyList.get(pos); pos ++; } return server; } }
Javasession
因爲serverWeightMap中的地址列表是動態的,隨時可能有機器上線、下線或者宕機,所以爲了不可能出現的併發問題,方法內部要新建局部變量serverMap,現將serverMap中的內容複製到線程本地,以免被多個線程修改。這樣可能會引入新的問題,複製之後serverWeightMap的修改沒法反映給serverMap,也就是說這一輪選擇服務器的過程當中,新增服務器或者下線服務器,負載均衡算法將沒法獲知。新增無所謂,若是有服務器下線或者宕機,那麼可能會訪問到不存在的地址。所以,服務調用端須要有相應的容錯處理,好比從新發起一次server選擇並調用。併發
對於當前輪詢的位置變量pos,爲了保證服務器選擇的順序性,須要在操做時對其加鎖,使得同一時刻只能有一個線程能夠修改pos的值,不然當pos變量被併發修改,則沒法保證服務器選擇的順序性,甚至有可能致使keyList數組越界。負載均衡
輪詢法的優勢在於:試圖作到請求轉移的絕對均衡。
輪詢法的缺點在於:爲了作到請求轉移的絕對均衡,必須付出至關大的代價,由於爲了保證pos變量修改的互斥性,須要引入重量級的悲觀鎖synchronized,這將會致使該段輪詢代碼的併發吞吐量發生明顯的降低。
經過系統的隨機算法,根據後端服務器的列表大小值來隨機選取其中的一臺服務器進行訪問。由機率統計理論能夠得知,隨着客戶端調用服務端的次數增多,
其實際效果愈來愈接近於平均分配調用量到後端的每一臺服務器,也就是輪詢的結果。
隨機法的代碼實現大體以下:
import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * @author ashang.peng@aliyun.com * @date 二月 07, 2017 */ class Random { public static String getServer() { // 重建一個Map,避免服務器的上下線致使的併發問題 Map<String, Integer> serverMap = new HashMap<String, Integer>(); serverMap.putAll(IpMap.serverWeightMap); // 取得Ip地址List Set keySet = serverMap.keySet(); ArrayList keyList = new ArrayList(); keyList.addAll(keySet); java.util.Random random = new java.util.Random(); int randomPos = random.nextInt(keyList.size()); return keyList.get(randomPos); } }
Java
總體代碼思路和輪詢法一致,先重建serverMap,再獲取到server列表。在選取server的時候,經過Random的nextInt方法取0~keyList.size()區間的一個隨機值,從而從服務器列表中隨機獲取到一臺服務器地址進行返回。基於機率統計的理論,吞吐量越大,隨機算法的效果越接近於輪詢算法的效果。
源地址哈希的思想是根據獲取客戶端的IP地址,經過哈希函數計算獲得的一個數值,用該數值對服務器列表的大小進行取模運算,獲得的結果即是客服端要訪問服務器的序號。採用源地址哈希法進行負載均衡,同一IP地址的客戶端,當後端服務器列表不變時,它每次都會映射到同一臺後端服務器進行訪問。
源地址哈希算法的代碼實現大體以下:
import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * @author ashang.peng@aliyun.com * @date 二月 07, 2017 */ class Hash { public static String getServer() { // 重建一個Map,避免服務器的上下線致使的併發問題 Map<String, Integer> serverMap = new HashMap<String, Integer>(); serverMap.putAll(IpMap.serverWeightMap); // 取得Ip地址List Set keySet = serverMap.keySet(); ArrayList keyList = new ArrayList(); keyList.addAll(keySet); // 在Web應用中可經過HttpServlet的getRemoteIp方法獲取 String remoteIp = "127.0.0.1"; int hashCode = remoteIp.hashCode(); int serverListSize = keyList.size(); int serverPos = hashCode % serverListSize; return keyList.get(serverPos); } }
Java
前兩部分和輪詢法、隨機法同樣就不說了,差異在於路由選擇部分。經過客戶端的ip也就是remoteIp,取得它的Hash值,對服務器列表的大小取模,結果即是選用的服務器在服務器列表中的索引值。
源地址哈希法的優勢在於:保證了相同客戶端IP地址將會被哈希到同一臺後端服務器,直到後端服務器列表變動。根據此特性能夠在服務消費者與服務提供者之間創建有狀態的session會話。
源地址哈希算法的缺點在於:除非集羣中服務器的很是穩定,基本不會上下線,不然一旦有服務器上線、下線,那麼經過源地址哈希算法路由到的服務器是服務器上線、下線前路由到的服務器的機率很是低,若是是session則取不到session,若是是緩存則可能引起"雪崩"。若是這麼解釋不適合明白,能夠看我以前的一篇文章MemCache超詳細解讀,一致性Hash算法部分。
不一樣的後端服務器可能機器的配置和當前系統的負載並不相同,所以它們的抗壓能力也不相同。給配置高、負載低的機器配置更高的權重,讓其處理更多的請;而配置低、負載高的機器,給其分配較低的權重,下降其系統負載,加權輪詢能很好地處理這一問題,並將請求順序且按照權重分配到後端。加權輪詢法的代碼實現大體以下:
import java.util.*; /** * @author ashang.peng@aliyun.com * @date 二月 07, 2017 */ class WeightRoundRobin { private static Integer pos; public static String getServer() { // 重建一個Map,避免服務器的上下線致使的併發問題 Map<String, Integer> serverMap = new HashMap<String, Integer>(); serverMap.putAll(IpMap.serverWeightMap); // 取得Ip地址List Set keySet = serverMap.keySet(); Iterator iterator = keySet.iterator(); List serverList = new ArrayList(); while (iterator.hasNext()) { String server = iterator.next(); int weight = serverMap.get(server); for (int i = 0; i < weight; i++) serverList.add(server); } String server = null; synchronized (pos) { // if (pos > keySet.size()) 感謝網友「紫寒」的指正,確實須要訂正為「serverList」應爲此時serverList 大小和keySet大小是有可能不一致的。有可能就達不到權重的效果 if (pos > serverList.size()-1) pos = 0; server = serverList.get(pos); pos ++; } return server; } }
Java
與輪詢法相似,只是在獲取服務器地址以前增長了一段權重計算的代碼,根據權重的大小,將地址重複地增長到服務器地址列表中,權重越大,該服務器每輪所得到的請求數量越多。
與加權輪詢法同樣,加權隨機法也根據後端機器的配置,系統的負載分配不一樣的權重。不一樣的是,它是按照權重隨機請求後端服務器,而非順序。
import java.util.*; /** * @author ashang.peng@aliyun.com * @date 二月 07, 2017 */ class WeightRandom { public static String getServer() { // 重建一個Map,避免服務器的上下線致使的併發問題 Map<String, Integer> serverMap = new HashMap<String, Integer>(); serverMap.putAll(IpMap.serverWeightMap); // 取得Ip地址List Set keySet = serverMap.keySet(); Iterator iterator = keySet.iterator(); List serverList = new ArrayList(); while (iterator.hasNext()) { String server = iterator.next(); int weight = serverMap.get(server); for (int i = 0; i < weight; i++) serverList.add(server); } java.util.Random random = new java.util.Random(); int randomPos = random.nextInt(serverList.size()); return serverList.get(randomPos); } }
Java
這段代碼至關因而隨機法和加權輪詢法的結合,比較好理解,就不解釋了。
最小鏈接數算法比較靈活和智能,因爲後端服務器的配置不盡相同,對於請求的處理有快有慢,它是根據後端服務器當前的鏈接狀況,動態地選取其中當前
積壓鏈接數最少的一臺服務器來處理當前的請求,儘量地提升後端服務的利用效率,將負責合理地分流到每一臺服務器。
前面幾種方法費盡心思來實現服務消費者請求次數分配的均衡,固然這麼作是沒錯的,能夠爲後端的多臺服務器平均分配工做量,最大程度地提升服務器的利用率,可是實際狀況是否真的如此?實際狀況中,請求次數的均衡真的能表明負載的均衡嗎?這是一個值得思考的問題。
上面的問題,再換一個角度來講就是:之後端服務器的視角來觀察系統的負載,而非請求發起方來觀察。最小鏈接數法便屬於此類。
最小鏈接數算法比較靈活和智能,因爲後端服務器的配置不盡相同,對於請求的處理有快有慢,它正是根據後端服務器當前的鏈接狀況,動態地選取其中當前積壓鏈接數最少的一臺服務器來處理當前請求,儘量地提升後端服務器的利用效率,將負載合理地分流到每一臺機器。因爲最小鏈接數設計服務器鏈接數的彙總和感知,設計與實現較爲繁瑣,此處就不說它的實現了。
附了一個說明「NGINX的實現緣由,你們能夠看看":