首先給大家介紹下什么是負(fù)載均衡(來(lái)自百科) 負(fù)載均衡建立在現(xiàn)有網(wǎng)絡(luò)結(jié)構(gòu)之上,它提供了一種廉價(jià)有效透明的方法擴(kuò)展 網(wǎng)絡(luò)設(shè)備和 服務(wù)器的帶寬、增加 吞吐量、加強(qiáng)網(wǎng)絡(luò)數(shù)據(jù)處理能力、提高網(wǎng)絡(luò)的靈活性和可用性。 負(fù)載均衡,英文名稱為L(zhǎng)oad Balance,其意思就是分?jǐn)偟蕉鄠€(gè)操作單元上進(jìn)行執(zhí)行,例如Web 服務(wù)器、 FTP服務(wù)器、 企業(yè)關(guān)鍵應(yīng)用服務(wù)器和其它關(guān)鍵任務(wù)服務(wù)器等,從而共同完成工作任務(wù)。 本文講述的是"將外部發(fā)送來(lái)的請(qǐng)求均勻分配到對(duì)稱結(jié)構(gòu)中的某一臺(tái)服務(wù)器上"的各種算法,并以Java代碼演示每種算法的具體實(shí)現(xiàn),OK,下面進(jìn)入正題,在進(jìn)入正題前,先寫(xiě)一個(gè)類來(lái)模擬Ip列表: import java.util.HashMap;/** * @author ashang.peng@aliyun.com * @date 二月 07, 2017 */public class IpMap { // 待路由的Ip列表,Key代表Ip,Value代表該Ip的權(quán)重 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); // 權(quán)重為4 serverWeightMap.put("192.168.1.102", 4); serverWeightMap.put("192.168.1.103", 1); serverWeightMap.put("192.168.1.104", 1); // 權(quán)重為3 serverWeightMap.put("192.168.1.105", 3); serverWeightMap.put("192.168.1.106", 1); // 權(quán)重為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); } } 輪詢(Round Robin)法輪詢調(diào)度算法的原理是每一次把來(lái)自用戶的請(qǐng)求輪流分配給內(nèi)部中的服務(wù)器,從1開(kāi)始,直到N(內(nèi)部服務(wù)器個(gè)數(shù)),然后重新開(kāi)始循環(huán)。算法的優(yōu)點(diǎn)是其簡(jiǎn)潔性,它無(wú)需記錄當(dāng)前所有連接的狀態(tài),所以它是一種無(wú)狀態(tài)調(diào)度。 其代碼實(shí)現(xià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() { // 重建一個(gè)Map,避免服務(wù)器的上下線導(dǎo)致的并發(fā)問(wèn)題 Map<String, Integer> serverMap = new HashMap<String, Integer>(); serverMap.putAll(IpMap.serverWeightMap); // 取得Ip地址List Set<String> keySet = serverMap.keySet(); ArrayList<String> keyList = new ArrayList<String>(); keyList.addAll(keySet); String server = null; synchronized (pos) { if (pos > keySet.size()) pos = 0; server = keyList.get(pos); pos ++; } return server; } } 由于serverWeightMap中的地址列表是動(dòng)態(tài)的,隨時(shí)可能有機(jī)器上線、下線或者宕機(jī),因此為了避免可能出現(xiàn)的并發(fā)問(wèn)題,方法內(nèi)部要新建局部變量serverMap,現(xiàn)將serverMap中的內(nèi)容復(fù)制到線程本地,以避免被多個(gè)線程修改。這樣可能會(huì)引入新的問(wèn)題,復(fù)制以后serverWeightMap的修改無(wú)法反映給serverMap,也就是說(shuō)這一輪選擇服務(wù)器的過(guò)程中,新增服務(wù)器或者下線服務(wù)器,負(fù)載均衡算法將無(wú)法獲知。新增無(wú)所謂,如果有服務(wù)器下線或者宕機(jī),那么可能會(huì)訪問(wèn)到不存在的地址。因此,服務(wù)調(diào)用端需要有相應(yīng)的容錯(cuò)處理,比如重新發(fā)起一次server選擇并調(diào)用。 對(duì)于當(dāng)前輪詢的位置變量pos,為了保證服務(wù)器選擇的順序性,需要在操作時(shí)對(duì)其加鎖,使得同一時(shí)刻只能有一個(gè)線程可以修改pos的值,否則當(dāng)pos變量被并發(fā)修改,則無(wú)法保證服務(wù)器選擇的順序性,甚至有可能導(dǎo)致keyList數(shù)組越界。 輪詢法的優(yōu)點(diǎn)在于:試圖做到請(qǐng)求轉(zhuǎn)移的絕對(duì)均衡。 輪詢法的缺點(diǎn)在于:為了做到請(qǐng)求轉(zhuǎn)移的絕對(duì)均衡,必須付出相當(dāng)大的代價(jià),因?yàn)闉榱吮WCpos變量修改的互斥性,需要引入重量級(jí)的悲觀鎖synchronized,這將會(huì)導(dǎo)致該段輪詢代碼的并發(fā)吞吐量發(fā)生明顯的下降 隨機(jī)(Random)法通過(guò)系統(tǒng)的隨機(jī)算法,根據(jù)后端服務(wù)器的列表大小值來(lái)隨機(jī)選取其中的一臺(tái)服務(wù)器進(jìn)行訪問(wèn)。由概率統(tǒng)計(jì)理論可以得知,隨著客戶端調(diào)用服務(wù)端的次數(shù)增多。 其實(shí)際效果越來(lái)越接近于平均分配調(diào)用量到后端的每一臺(tái)服務(wù)器,也就是輪詢的結(jié)果。 隨機(jī)法的代碼實(shí)現(xià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 Random { public static String getServer() { // 重建一個(gè)Map,避免服務(wù)器的上下線導(dǎo)致的并發(fā)問(wèn)題 Map<String, Integer> serverMap = new HashMap<String, Integer>(); serverMap.putAll(IpMap.serverWeightMap); // 取得Ip地址List Set<String> keySet = serverMap.keySet(); ArrayList<String> keyList = new ArrayList<String>(); keyList.addAll(keySet); java.util.Random random = new java.util.Random(); int randomPos = random.nextInt(keyList.size()); return keyList.get(randomPos); } } 整體代碼思路和輪詢法一致,先重建serverMap,再獲取到server列表。在選取server的時(shí)候,通過(guò)Random的nextInt方法取0~keyList.size()區(qū)間的一個(gè)隨機(jī)值,從而從服務(wù)器列表中隨機(jī)獲取到一臺(tái)服務(wù)器地址進(jìn)行返回?;诟怕式y(tǒng)計(jì)的理論,吞吐量越大,隨機(jī)算法的效果越接近于輪詢算法的效果。 源地址哈希(Hash)法源地址哈希的思想是根據(jù)獲取客戶端的IP地址,通過(guò)哈希函數(shù)計(jì)算得到的一個(gè)數(shù)值,用該數(shù)值對(duì)服務(wù)器列表的大小進(jìn)行取模運(yùn)算,得到的結(jié)果便是客服端要訪問(wèn)服務(wù)器的序號(hào)。采用源地址哈希法進(jìn)行負(fù)載均衡,同一IP地址的客戶端,當(dāng)后端服務(wù)器列表不變時(shí),它每次都會(huì)映射到同一臺(tái)后端服務(wù)器進(jìn)行訪問(wèn)。、 源地址哈希算法的代碼實(shí)現(xià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 Hash { public static String getServer() { // 重建一個(gè)Map,避免服務(wù)器的上下線導(dǎo)致的并發(fā)問(wèn)題 Map<String, Integer> serverMap = new HashMap<String, Integer>(); serverMap.putAll(IpMap.serverWeightMap); // 取得Ip地址List Set<String> keySet = serverMap.keySet(); ArrayList<String> keyList = new ArrayList<String>(); keyList.addAll(keySet); // 在Web應(yīng)用中可通過(guò)HttpServlet的getRemoteIp方法獲取 String remoteIp = "127.0.0.1"; int hashCode = remoteIp.hashCode(); int serverListSize = keyList.size(); int serverPos = hashCode % serverListSize; return keyList.get(serverPos); } } 前兩部分和輪詢法、隨機(jī)法一樣就不說(shuō)了,差別在于路由選擇部分。通過(guò)客戶端的ip也就是remoteIp,取得它的Hash值,對(duì)服務(wù)器列表的大小取模,結(jié)果便是選用的服務(wù)器在服務(wù)器列表中的索引值。 源地址哈希法的優(yōu)點(diǎn)在于:保證了相同客戶端IP地址將會(huì)被哈希到同一臺(tái)后端服務(wù)器,直到后端服務(wù)器列表變更。根據(jù)此特性可以在服務(wù)消費(fèi)者與服務(wù)提供者之間建立有狀態(tài)的session會(huì)話。 源地址哈希算法的缺點(diǎn)在于:除非集群中服務(wù)器的非常穩(wěn)定,基本不會(huì)上下線,否則一旦有服務(wù)器上線、下線,那么通過(guò)源地址哈希算法路由到的服務(wù)器是服務(wù)器上線、下線前路由到的服務(wù)器的概率非常低,如果是session則取不到session,如果是緩存則可能引發(fā)"雪崩"。如果這么解釋不適合明白,可以看我之前的一篇文章MemCache超詳細(xì)解讀,一致性Hash算法部分。 加權(quán)輪詢(Weight Round Robin)法不同的后端服務(wù)器可能機(jī)器的配置和當(dāng)前系統(tǒng)的負(fù)載并不相同,因此它們的抗壓能力也不相同。給配置高、負(fù)載低的機(jī)器配置更高的權(quán)重,讓其處理更多的請(qǐng);而配置低、負(fù)載高的機(jī)器,給其分配較低的權(quán)重,降低其系統(tǒng)負(fù)載,加權(quán)輪詢能很好地處理這一問(wèn)題,并將請(qǐng)求順序且按照權(quán)重分配到后端。加權(quán)輪詢法的代碼實(shí)現(xiàn)大致如下: import java.util.*;/** * @author ashang.peng@aliyun.com * @date 二月 07, 2017 */class WeightRoundRobin { private static Integer pos; public static String getServer() { // 重建一個(gè)Map,避免服務(wù)器的上下線導(dǎo)致的并發(fā)問(wèn)題 Map<String, Integer> serverMap = new HashMap<String, Integer>(); serverMap.putAll(IpMap.serverWeightMap); // 取得Ip地址List Set<String> keySet = serverMap.keySet(); Iterator<String> iterator = keySet.iterator(); List<String> serverList = new ArrayList<String>(); 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()) pos = 0; server = serverList.get(pos); pos ++; } return server; } } 與輪詢法類似,只是在獲取服務(wù)器地址之前增加了一段權(quán)重計(jì)算的代碼,根據(jù)權(quán)重的大小,將地址重復(fù)地增加到服務(wù)器地址列表中,權(quán)重越大,該服務(wù)器每輪所獲得的請(qǐng)求數(shù)量越多。 加權(quán)隨機(jī)(Weight Random)法與加權(quán)輪詢法一樣,加權(quán)隨機(jī)法也根據(jù)后端機(jī)器的配置,系統(tǒng)的負(fù)載分配不同的權(quán)重。不同的是,它是按照權(quán)重隨機(jī)請(qǐng)求后端服務(wù)器,而非順序。 import java.util.*;/** * @author ashang.peng@aliyun.com * @date 二月 07, 2017 */ class WeightRandom { public static String getServer() { // 重建一個(gè)Map,避免服務(wù)器的上下線導(dǎo)致的并發(fā)問(wèn)題 Map<String, Integer> serverMap = new HashMap<String, Integer>(); serverMap.putAll(IpMap.serverWeightMap); // 取得Ip地址List Set<String> keySet = serverMap.keySet(); Iterator<String> iterator = keySet.iterator(); List<String> serverList = new ArrayList<String>(); 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); } } 這段代碼相當(dāng)于是隨機(jī)法和加權(quán)輪詢法的結(jié)合,比較好理解,就不解釋了。 最小連接數(shù)(Least Connections)法最小連接數(shù)算法比較靈活和智能,由于后端服務(wù)器的配置不盡相同,對(duì)于請(qǐng)求的處理有快有慢,它是根據(jù)后端服務(wù)器當(dāng)前的連接情況,動(dòng)態(tài)地選取其中當(dāng)前 積壓連接數(shù)最少的一臺(tái)服務(wù)器來(lái)處理當(dāng)前的請(qǐng)求,盡可能地提高后端服務(wù)的利用效率,將負(fù)責(zé)合理地分流到每一臺(tái)服務(wù)器。 前面幾種方法費(fèi)盡心思來(lái)實(shí)現(xiàn)服務(wù)消費(fèi)者請(qǐng)求次數(shù)分配的均衡,當(dāng)然這么做是沒(méi)錯(cuò)的,可以為后端的多臺(tái)服務(wù)器平均分配工作量,最大程度地提高服務(wù)器的利用率,但是實(shí)際情況是否真的如此?實(shí)際情況中,請(qǐng)求次數(shù)的均衡真的能代表負(fù)載的均衡嗎?這是一個(gè)值得思考的問(wèn)題。 上面的問(wèn)題,再換一個(gè)角度來(lái)說(shuō)就是:以后端服務(wù)器的視角來(lái)觀察系統(tǒng)的負(fù)載,而非請(qǐng)求發(fā)起方來(lái)觀察。最小連接數(shù)法便屬于此類。 最小連接數(shù)算法比較靈活和智能,由于后端服務(wù)器的配置不盡相同,對(duì)于請(qǐng)求的處理有快有慢,它正是根據(jù)后端服務(wù)器當(dāng)前的連接情況,動(dòng)態(tài)地選取其中當(dāng)前積壓連接數(shù)最少的一臺(tái)服務(wù)器來(lái)處理當(dāng)前請(qǐng)求,盡可能地提高后端服務(wù)器的利用效率,將負(fù)載合理地分流到每一臺(tái)機(jī)器。由于最小連接數(shù)設(shè)計(jì)服務(wù)器連接數(shù)的匯總和感知,設(shè)計(jì)與實(shí)現(xiàn)較為繁瑣,此處就不說(shuō)它的實(shí)現(xiàn)了。 附了一個(gè)說(shuō)明“NGINX的實(shí)現(xiàn)原因,大家可以看看": 來(lái)自:DUZHI 相關(guān)鏈接
|
|