幾種簡單的負載均衡算法及其Java代碼實現(xiàn)

什么是負載均衡

負載均衡,英文名稱為Load Balance脑题,指由多臺服務器以對稱的方式組成一個服務器集合,每臺服務器都具有等價的地位,都可以單獨對外提供服務而無須其他服務器的輔助玄柠。通過某種負載分擔技術(shù),將外部發(fā)送來的請求均勻分配到對稱結(jié)構(gòu)中的某一臺服務器上诫舅,而接收到請求的服務器獨立地回應客戶的請求羽利。負載均衡能夠平均分配客戶請求到服務器陣列,借此提供快速獲取重要數(shù)據(jù)刊懈,解決大量并發(fā)訪問服務問題这弧,這種集群技術(shù)可以用最少的投資獲得接近于大型主機的性能。

負載均衡分為軟件負載均衡和硬件負載均衡虚汛,前者的代表是阿里章文嵩博士研發(fā)的LVS匾浪,后者則是均衡服務器比如F5,當然這只是提一下卷哩,不是重點户矢。
本文講述的是"將外部發(fā)送來的請求均勻分配到對稱結(jié)構(gòu)中的某一臺服務器上"的各種算法,并以Java代碼演示每種算法的具體實現(xiàn)殉疼,OK,下面進入正題捌年,在進入正題前瓢娜,先寫一個類來模擬Ip列表:

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)法

輪詢法即Round Robin法眠砾,其代碼實現(xiàn)大致如下:
public class RoundRobin{
  private static Integer pos = 0;
  public static String getServer(){
      // 重建一個Map,避免服務器的上下線導致的并發(fā)問題
      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中的地址列表是動態(tài)的托酸,隨時可能有機器上線褒颈、下線或者宕機柒巫,因此為了避免可能出現(xiàn)的并發(fā)問題,方法內(nèi)部要新建局部變量serverMap谷丸,現(xiàn)將serverMap中的內(nèi)容復制到線程本地堡掏,以避免被多個線程修改。這樣可能會引入新的問題刨疼,復制以后serverWeightMap的修改無法反映給serverMap泉唁,也就是說這一輪選擇服務器的過程中,新增服務器或者下線服務器揩慕,負載均衡算法將無法獲知亭畜。新增無所謂,如果有服務器下線或者宕機迎卤,那么可能會訪問到不存在的地址拴鸵。因此,服務調(diào)用端需要有相應的容錯處理蜗搔,比如重新發(fā)起一次server選擇并調(diào)用劲藐。
對于當前輪詢的位置變量pos,為了保證服務器選擇的順序性碍扔,需要在操作時對其加鎖瘩燥,使得同一時刻只能有一個線程可以修改pos的值,否則當pos變量被并發(fā)修改不同,則無法保證服務器選擇的順序性厉膀,甚至有可能導致keyList數(shù)組越界。

輪詢法的優(yōu)點在于:試圖做到請求轉(zhuǎn)移的絕對均衡二拐。
輪詢法的缺點在于:為了做到請求轉(zhuǎn)移的絕對均衡服鹅,必須付出相當大的代價,因為為了保證pos變量修改的互斥性百新,需要引入重量級的悲觀鎖synchronized企软,這將會導致該段輪詢代碼的并發(fā)吞吐量發(fā)生明顯的下降。

隨機(Random)法

通過系統(tǒng)隨機函數(shù)饭望,根據(jù)后端服務器列表的大小值來隨機選擇其中一臺進行訪問仗哨。由概率統(tǒng)計理論可以得知,隨著調(diào)用量的增大铅辞,其實際效果越來越接近于平均分配流量到每一臺后端服務器厌漂,也就是輪詢的效果。
隨機法的代碼實現(xiàn)大致如下:

public class Random{
  public static String getServer(){
    // 重建一個Map斟珊,避免服務器的上下線導致的并發(fā)問題
    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的時候旨椒,通過Random的nextInt方法取0~keyList.size()區(qū)間的一個隨機值晓褪,從而從服務器列表中隨機獲取到一臺服務器地址進行返回∽凵鳎基于概率統(tǒng)計的理論涣仿,吞吐量越大,隨機算法的效果越接近于輪詢算法的效果寥粹。

源地址哈希(Hash)法

源地址哈希的思想是獲取客戶端訪問的IP地址值变过,通過哈希函數(shù)計算得到一個數(shù)值,用該數(shù)值對服務器列表的大小進行取模運算涝涤,得到的結(jié)果便是要訪問的服務器的序號媚狰。源地址哈希算法的代碼實現(xiàn)大致如下:

public class Hash{
  public static String getServer(){
    // 重建一個Map,避免服務器的上下線導致的并發(fā)問題
    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應用中可通過HttpServlet的getRemoteIp方法獲取
    String remoteIp = "127.0.0.1";
    int hashCode = remoteIp.hashCode();
    int serverListSize = keyList.size();
    int serverPos = hashCode % serverListSize;
    
    return keyList.get(serverPos);
  }
}

前兩部分和輪詢法阔拳、隨機法一樣就不說了崭孤,差別在于路由選擇部分。通過客戶端的ip也就是remoteIp糊肠,取得它的Hash值辨宠,對服務器列表的大小取模,結(jié)果便是選用的服務器在服務器列表中的索引值货裹。
源地址哈希法的優(yōu)點在于:保證了相同客戶端IP地址將會被哈希到同一臺后端服務器嗤形,直到后端服務器列表變更。根據(jù)此特性可以在服務消費者與服務提供者之間建立有狀態(tài)的session會話弧圆。
源地址哈希算法的缺點在于:除非集群中服務器的非常穩(wěn)定赋兵,基本不會上下線,否則一旦有服務器上線搔预、下線霹期,那么通過源地址哈希算法路由到的服務器是服務器上線、下線前路由到的服務器的概率非常低拯田,如果是session則取不到session历造,如果是緩存則可能引發(fā)"雪崩"。一致性Hash算法部分船庇。

加權(quán)輪詢(Weight Round Robin)法

不同的服務器可能機器配置和當前系統(tǒng)的負載并不相同吭产,因此它們的抗壓能力也不盡相同,給配置高鸭轮、負載低的機器配置更高的權(quán)重垮刹,讓其處理更多的請求,而低配置张弛、高負載的機器,則給其分配較低的權(quán)重,降低其系統(tǒng)負載吞鸭。加權(quán)輪詢法可以很好地處理這一問題寺董,并將請求順序按照權(quán)重分配到后端。加權(quán)輪詢法的代碼實現(xiàn)大致如下:

public class WeightRoundRobin{
  private static Integer pos;
  public static String getServer(){
    // 重建一個Map刻剥,避免服務器的上下線導致的并發(fā)問題
    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;
  }
}

與輪詢法類似遮咖,只是在獲取服務器地址之前增加了一段權(quán)重計算的代碼,根據(jù)權(quán)重的大小造虏,將地址重復地增加到服務器地址列表中御吞,權(quán)重越大,該服務器每輪所獲得的請求數(shù)量越多漓藕。

加權(quán)隨機(Weight Random)法

與加權(quán)輪詢法類似陶珠,加權(quán)隨機法也是根據(jù)后端服務器不同的配置和負載情況來配置不同的權(quán)重。不同的是享钞,它是按照權(quán)重來隨機選擇服務器的揍诽,而不是順序。加權(quán)隨機法的代碼實現(xiàn)如下:

public class WeightRandom{
  public static String getServer(){
    // 重建一個Map栗竖,避免服務器的上下線導致的并發(fā)問題
    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);
  }
}

這段代碼相當于是隨機法和加權(quán)輪詢法的結(jié)合暑脆,比較好理解,就不解釋了狐肢。

最小連接數(shù)(Least Connections)法

前面幾種方法費盡心思來實現(xiàn)服務消費者請求次數(shù)分配的均衡添吗,當然這么做是沒錯的,可以為后端的多臺服務器平均分配工作量份名,最大程度地提高服務器的利用率碟联,但是實際情況是否真的如此?實際情況中同窘,請求次數(shù)的均衡真的能代表負載的均衡嗎玄帕?這是一個值得思考的問題。
上面的問題想邦,再換一個角度來說就是:以后端服務器的視角來觀察系統(tǒng)的負載裤纹,而非請求發(fā)起方來觀察。最小連接數(shù)法便屬于此類丧没。
最小連接數(shù)算法比較靈活和智能鹰椒,由于后端服務器的配置不盡相同,對于請求的處理有快有慢呕童,它正是根據(jù)后端服務器當前的連接情況漆际,動態(tài)地選取其中當前積壓連接數(shù)最少的一臺服務器來處理當前請求,盡可能地提高后端服務器的利用效率夺饲,將負載合理地分流到每一臺機器奸汇。由于最小連接數(shù)設計服務器連接數(shù)的匯總和感知施符,設計與實現(xiàn)較為繁瑣,此處就不說它的實現(xiàn)了擂找。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末戳吝,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子贯涎,更是在濱河造成了極大的恐慌听哭,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件塘雳,死亡現(xiàn)場離奇詭異陆盘,居然都是意外死亡,警方通過查閱死者的電腦和手機败明,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進店門隘马,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人肩刃,你說我怎么就攤上這事祟霍。” “怎么了盈包?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵沸呐,是天一觀的道長。 經(jīng)常有香客問我呢燥,道長崭添,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任叛氨,我火速辦了婚禮呼渣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘寞埠。我一直安慰自己屁置,他們只是感情好,可當我...
    茶點故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布仁连。 她就那樣靜靜地躺著蓝角,像睡著了一般。 火紅的嫁衣襯著肌膚如雪饭冬。 梳的紋絲不亂的頭發(fā)上使鹅,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天,我揣著相機與錄音昌抠,去河邊找鬼患朱。 笑死,一個胖子當著我的面吹牛炊苫,可吹牛的內(nèi)容都是我干的裁厅。 我是一名探鬼主播冰沙,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼姐直!你這毒婦竟也來了倦淀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤声畏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后姻成,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體插龄,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年科展,在試婚紗的時候發(fā)現(xiàn)自己被綠了均牢。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡才睹,死狀恐怖徘跪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情琅攘,我是刑警寧澤垮庐,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站坞琴,受9級特大地震影響哨查,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜剧辐,卻給世界環(huán)境...
    茶點故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一寒亥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧荧关,春花似錦溉奕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至檀轨,卻和暖如春胸竞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背参萄。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工卫枝, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人讹挎。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓校赤,卻偏偏與公主長得像吆玖,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子马篮,可洞房花燭夜當晚...
    茶點故事閱讀 45,047評論 2 355

推薦閱讀更多精彩內(nèi)容