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

負載均衡演算法示例

宣告:本文根據魯班學院周瑜老師課程筆記整理而來

負載均衡的介紹?

  負載均衡,英文名稱Load Balance,指由多臺伺服器以對稱的方式組成一個伺服器集合,每臺伺服器都具有等價的地位,都可以單獨對外提供服務而無須其他伺服器的輔助。   通過某種負載分擔技術,將外部發送來的請求均勻分佈到對稱結構中的某一臺伺服器上,而接收到請求的伺服器獨立地迴應客戶的請求。   負載均衡能夠平均分配客戶請求到伺服器陣列,藉此提供快速獲取重要資料,解決大量併發訪問伺服器問題,這種叢集技術可以用最少的投資獲得接近於大型主機的效能。 負載均衡方式?   負載均衡分為軟體負載均衡和硬體負載均衡。 軟體負載均衡:   常見的負載均衡軟體有Nginx,LVS,HAProxy。   (總結)Nginx/LVS/HAProxy負載均衡軟體的優缺點:http://www.ha97.com/5646.html   三大主流軟體負載均衡器對比(LVS,Nginx,HAProxy):http://www.21yunwei.com/archives/5824 硬體負載均衡:   常見的負載均衡硬體有Array,F5. 負載均衡演算法?   常見的負載均衡演算法有:隨機演算法,加權輪詢演算法,一致性hash演算法,最小活躍數演算法。

演算法的前提條件:

  定義一個伺服器列表,每個負載均衡的演算法會從中挑選一個伺服器作為演算法的結果:

/**
 * 定義一個伺服器列表,每個負載均衡演算法會從中挑選一個伺服器作為演算法結果
 * @author Administrator
 *
 */
public class ServerIps {

    public static final List<String> LIST = Arrays.asList(
            "192.168.0.1",
            "192.168.0.2",
            "192.168.0.3",
            "192.168.0.4",
            
"192.168.0.5", "192.168.0.6", "192.168.0.7", "192.168.0.8", "192.168.0.9", "192.168.0.10"); }

隨機演算法--RandomLoadBalance

  首先來個最簡單的隨機演算法的實現。

/**
 * 隨機演算法
 * @author Administrator
 *
 */
public class JavaRandom {
    public static String getServer(){
        
//生成一個隨機數作為list的下標值 Random random = new Random(); int randomPos = random.nextInt(ServerIps.LIST.size()); return ServerIps.LIST.get(randomPos); } public static void main(String[] args) { //連續呼叫10次 for(int i=0;i<10;i++){ System.out.println(getServer()); } } }

  執行結果:

  

  當呼叫次數比較少時,Random產生的隨機數可能會比較集中,此時多數請求會落到同一臺伺服器上,只有在經過多次請求後,才能使用請求進行“均勻”分配,呼叫量少這一點對我們來說不算什麼,因為負載均衡機制正是為了應對請求量多的情況。所以隨機演算法也是用的比較多的一種演算法。

  但是,上面的隨機演算法適用於每臺機器的效能差不多的時候,實際上,上產中可能某些機器的效能更高一點,它可以處理更多的請求,所以我們可以對每臺機器設定一個權重。

  我們在ServerIps類中增加伺服器權重對應關係的Map,設定權重之和為50。

    //增加權重
    public static final Map<String,Integer> WEIGHT_LIST=new HashMap<>();
    static{
        WEIGHT_LIST.put("192.168.0.1", 1);
        WEIGHT_LIST.put("192.168.0.2", 8);
        WEIGHT_LIST.put("192.168.0.3", 3);
        WEIGHT_LIST.put("192.168.0.4", 6);
        WEIGHT_LIST.put("192.168.0.5", 5);
        WEIGHT_LIST.put("192.168.0.6", 5);
        WEIGHT_LIST.put("192.168.0.7", 4);
        WEIGHT_LIST.put("192.168.0.8", 7);
        WEIGHT_LIST.put("192.168.0.9", 2);
        WEIGHT_LIST.put("192.168.0.10", 9);
    }

  那麼現在的隨機演算法應該要改成權重隨機演算法,當呼叫量比較多的時候,伺服器使用的分佈應該近似對應權重的分佈。

權重隨機演算法:

  簡單的思路是,把每個伺服器按它對應的伺服器進行復制。

/**
 * 增加權重的隨機演算法
 * @author Administrator
 *
 */
public class JavaWeightRandom {
    public static String getServer(){
        //1.首先根據權重進行list陣列的賦值
        List<String> ips = new ArrayList<>();
        for(String ip : ServerIps.WEIGHT_LIST.keySet()){
            Integer weight = ServerIps.WEIGHT_LIST.get(ip);
            //按權重進行賦值
            for(int i = 0;i<weight;i++){
                ips.add(ip);
            }
        }
        //2.在list陣列中使用隨機演算法進行取值
        //生成一個隨機數作為list的下標值
        Random random = new Random();
        int randomPos = random.nextInt(ips.size());
        return ips.get(randomPos);
    }
    public static void main(String[] args) {
        //連續呼叫10次
        for(int i = 0;i<10;i++){
            System.out.println(getServer());
        }
    }
}

  執行結果:

  

  這種實現方法在遇到權重之和特別大的時候,就會比較消耗記憶體,因為需要對IP地址進行復制,權重之和越大,那麼上下文的IPS就需要越多的記憶體,下面介紹另外一種實現思路。

  假設我們有一組伺服器servers=[A,B,C],它們對應的權重為weights=[5,3,2],權重總和為10,現在把這些權重值平鋪在一維座標值上,[0,5)區間屬於伺服器A,[5,8)區間屬於伺服器B,[8,10)區間屬於伺服器C,接下來通過隨機數生成器生成一個範圍在[0,10)之間的隨機數,然後計算這個隨機數會落到哪個區間上,比如:數字3會落在伺服器A對應的區間上,此時返回伺服器A即可,權重越大的機器在座標軸上對應的區間範圍就越大,因此隨機數生成器生成的數字就會有更大的概率落到此區間上。只要隨機數生成器產生的隨機數分不行很好,在經過多次選擇之後,每個伺服器被選中的次數比例接近其權重比例。比如,經過一萬次選擇後,伺服器A被選中的次數大約為5000次,伺服器B被選中的次數大約為3000次,伺服器C被選中的次數大約為2000次。

  假設現在的隨機數offset=7;

  1.offset<5 is false,所以不在[0,5)區間,將offset = offset-5(offset=2)

  2.offset<3 is true,所以處在[5,8)區間,所以應該選擇伺服器B

/**
 * 將權重模擬在一維座標軸上
 * @author Administrator
 *
 */
public class JavaWeightRandom2 {
    public static String getServer(){
        //定義總權重屬性
        int totalWeight = 0;
        boolean sameWeight = true;//如果所有權重都相等,那麼就隨機一個IP
        //得到所有的權重,生成一維座標軸
        Object[] weights = ServerIps.WEIGHT_LIST.values().toArray();
        for(int i =0;i<weights.length;i++){
            Integer weight = (Integer) weights[i];
            totalWeight += weight;
            //判斷是否所有權重都相等
            if(sameWeight && i>0 && !weight.equals(weights[i-1])){
                sameWeight = false;
            }
        }
        
        //生成一個隨機數作為list的下標值
        Random random = new Random();
        int randomPos = random.nextInt(totalWeight);
        if(!sameWeight){//不是所有權重都相等時
            for(String ip : ServerIps.WEIGHT_LIST.keySet()){
                Integer value = ServerIps.WEIGHT_LIST.get(ip);
                if(randomPos < value){
                    return ip;
                }
                randomPos = randomPos -value;
            }
        }
        //如果所有權重都相等直接隨機返回一個
        return  (String) ServerIps.WEIGHT_LIST.keySet().toArray()[new 
                 Random().nextInt(ServerIps.WEIGHT_LIST.size())];
    }
    public static void main(String[] args) {
        //連續呼叫10次
        for(int i=0;i<10;i++){
            System.out.println(getServer());
        }
    }
}

  測試結果:

  

這就是另外一種權重隨機演算法

輪詢演算法--RoundRobinLoadBalance

  簡單的輪詢演算法很簡單

  

/**
 * 簡單的輪詢演算法
 * @author Administrator
 *
 */
public class JavaRoundRobin {
    //當前迴圈的位置
    private static Integer pos = 0;
    public static String getServer(){
        String ip = null;
        //pos同步
        synchronized (pos) {
            if(pos >= ServerIps.LIST.size()){
                pos = 0;
            }
            ip = ServerIps.LIST.get(pos);
            pos++;
        }
        return ip;
    }
    public static void main(String[] args) {
        for(int i=0;i<10;i++){
            System.out.println(getServer());
        }
    }
}

  測試結果:

  

  這種演算法很簡單也很公平,每臺伺服器輪流來進行服務,但是,有的機器效能好,所以應當能者多勞,和隨機演算法一樣,加上權重這個維度之後,其中一種實現方法就是複製發,這種複製演算法和隨機演算法一樣,缺點都是比較消耗記憶體,那麼有沒有用其他實現方法?我們下面來介紹一種演算法。