1. 程式人生 > >如何實現負載均衡,有哪些演算法可以實現?

如何實現負載均衡,有哪些演算法可以實現?

【前言】 負載均衡技術對於中大型網站的效能提高有著很大的優勢,最近在學習《大型網站技術的架構》,其中對於負載均衡技術有一些介紹,將學習的經驗總結一下,分享下。多交流。

【協議層】http重定向協議實現負載均衡 原理:根據使用者的http請求計算出一個真實的web伺服器地址,並將該web伺服器地址寫入http重定向響應中返回給瀏覽器,由瀏覽器重新進行訪問。

如圖: 在這裡插入圖片描述

優點:比較簡單 缺點:瀏覽器需要零次請求伺服器才能完成一次訪問,效能較差。 http重定向伺服器自身的處理能力可能成為瓶頸。 使用http302響應重定向,有可能使搜尋引擎判斷為SEO作弊,降低搜尋排名。

【協議層】dns域名解析負載均衡 原理:在DNS伺服器上配置多個域名對應IP的記錄。例如一個域名www.baidu.com對應一組web伺服器IP地址,域名解析時經過DNS伺服器的演算法將一個域名請求分配到合適的真實伺服器上。

如圖: 在這裡插入圖片描述

   優點:將負載均衡的工作交給了DNS,省卻了網站管理維護負載均衡伺服器的麻煩,同事許多DNS還支援基於地理位置的域名解析,將域名解析成距離使用者地理最近的一個伺服器地址,加快訪問速度嗎,改善效能。
   缺點:目前的DNS解析是多級解析,每一級DNS都可能化快取記錄A,當摸一伺服器下線後,該伺服器對應的DNS記錄A可能仍然存在,導致分配到該伺服器的使用者訪問失敗。
    DNS負載均衡的控制權在域名服務商手裡,網站可能無法做出過多的改善和管理。
    不能夠按伺服器的處理能力來分配負載。DNS負載均衡採用的是簡單的輪詢演算法,不能區分伺服器之間的差異,不能反映伺服器當前執行狀態,所以其的負載均衡效果並不是太好。
    可能會造成額外的網路問題。為了使本DNS伺服器和其他DNS伺服器及時互動,保證DNS資料及時更新,使地址能隨機分配,一般都要將DNS的重新整理時間設定的較小,但太小將會使DNS流量大增造成額外的網路問題。

【協議層】反向代理負載均衡 原理:反向代理處於web伺服器這邊,反向代理伺服器提供負載均衡的功能,同時管理一組web伺服器,它根據負載均衡演算法將請求的瀏覽器訪問轉發到不同的web伺服器處理,處理結果經過反向伺服器返回給瀏覽器。

如圖: 在這裡插入圖片描述

   例如:瀏覽器訪問請求的地址是反向代理伺服器的地址114.100.80.10,反向代理伺服器收到請求,經過負載均衡演算法後得到一個真實實體地址10.0.03,並將請求結果發給真實無服務,真實伺服器處理完後通過反向代理伺服器返回給請求使用者。
  優點:部署簡單,處於http協議層面。
  缺點:使用了反向代理伺服器後,web 伺服器地址不能直接暴露在外,因此web伺服器不需要使用外部IP地址,而反向代理服務作為溝通橋樑就需要配置雙網絡卡、外部內部兩套IP地址。

【網路層】IP負載均衡 原理:在網路層通過修改目標地址進行負載均衡。 如圖:

在這裡插入圖片描述

   使用者訪問請求到達負載均衡伺服器,負載均衡伺服器在作業系統核心程序獲取網路資料包,根據演算法得到一臺真實伺服器地址,然後將使用者請求的目標地址修改成該真實伺服器地址,資料處理完後返回給負載均衡伺服器,負載均衡伺服器收到響應後將自身的地址修改成原使用者訪問地址後再講資料返回回去。類似於反向伺服器負載均衡。
   
   優點:在響應請求時速度較反向伺服器負載均衡要快。
   缺點:當請求資料較大(大型視訊或檔案)時,速度較慢。

【鏈路層】資料鏈路層負載均衡 原理:在資料鏈路層修改Mac地址進行負載均衡。

如圖: 在這裡插入圖片描述

    負載均衡伺服器的IP和它所管理的web 服務群的虛擬IP一致;
    負載均衡資料分發過程中不修改訪問地址的IP地址,而是修改Mac地址;
    通過這兩點達到不修改資料包的原地址和目標地址就可以進行正常的訪問。

   優點:不需要負載均衡伺服器進行地址的轉換。
             資料響應時不需要經過負載均衡伺服器。
    缺點:負載均衡伺服器的網絡卡頻寬要求較高。

   目前連路程負載均衡是特別常見的一種手段,典型的產品有LVS(Linux Virtual Server)。

演算法實現 常見的負載均衡演算法包括輪詢法、隨機法、源地址雜湊法、加權輪詢法、加權隨機法、最小連線法等。具體實現如下:

import java.util.*;

public class LoadBalancing {

    static Map<String,Integer> serverWeightMap = new HashMap<String,Integer>();
    static Integer pos = 0;
    static{
        serverWeightMap.put("192.168.1.100",1);
        serverWeightMap.put("192.168.1.101",1);
        serverWeightMap.put("192.168.1.102",4);
        serverWeightMap.put("192.168.1.103",1);
        serverWeightMap.put("192.168.1.104",1);
        serverWeightMap.put("192.168.1.105",3);
        serverWeightMap.put("192.168.1.106",1);
        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",3);
    }

    //輪詢
    public static String testRoundRobin(){
        //重新建立一個map,避免出現由於伺服器上線和下線導致的併發問題
        Map<String,Integer> serverMap = new HashMap<String, Integer>();
        serverMap.putAll(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 = new Integer(0);
            }
            server = keyList.get(pos);
            pos++;
        }
        return server;

    }

    //隨機
    public static String testRandom(){
        //重新建立一個map,避免出現由於伺服器上線和下線導致的併發問題
        Map<String,Integer> serverMap = new HashMap<String,Integer>();
        serverMap.putAll(serverWeightMap);


        //取得ip地址list
        Set<String> keySet = serverMap.keySet();
        ArrayList<String> keyList = new ArrayList<String>();
        keyList.addAll(keySet);

        Random random = new Random();
        int randomPos = random.nextInt(keyList.size());

        String server = keyList.get(randomPos);
        return server;
    }

    //源地址雜湊(hash)法
    public static String testConsumerHash(String remoteIp){
        //重新建立一個map,避免出現由於伺服器上線和下線導致的併發問題
        Map<String,Integer> serverMap = new HashMap<String,Integer>();
        serverMap.putAll(serverWeightMap);


        //取得ip地址list
        Set<String> keySet = serverMap.keySet();
        ArrayList<String> keyList = new ArrayList<String>();
        keyList.addAll(keySet);

        int hashCode = remoteIp.hashCode();
        int serverListSize = keyList.size();
        int serverPos = hashCode % serverListSize;

        return keyList.get(serverPos);
    }

    //加權輪詢
    public static String testWeightRoundRobin(){
        //重新建立一個map,避免由於伺服器上線和下線導致的併發問題
        Map<String,Integer> serverMap = new HashMap<String,Integer>();
        serverMap.putAll(serverWeightMap);

        //取得ip地址list
        Set<String> keySet = serverMap.keySet();
        Iterator<String> it = keySet.iterator();

        List<String> serverList = new ArrayList<String>();
        while(it.hasNext()){
            String server = it.next();
            Integer weight = serverMap.get(server);
            for(int i=0;i<weight;i++){
                serverList.add(server);
            }
        }

        String server = null;
        synchronized (pos){
            if(pos >= serverList.size()){
                pos = new Integer(0);
            }
            server = serverList.get(pos);
            pos++;
        }
        return server;
    }

    //加權隨機
    public static String testWeightRandom(){
        //重新建立一個map,避免出現由於伺服器上線和下線導致的併發問題
        Map<String,Integer> serverMap = new HashMap<String,Integer>();
        serverMap.putAll(serverWeightMap);


        //取得ip地址list
        Set<String> keySet = serverMap.keySet();
        ArrayList<String> serverList = new ArrayList<String>();

        Iterator<String> iterator = keySet.iterator();
        while(iterator.hasNext()){
            String server = iterator.next();
            Integer weight = serverMap.get(server);
            for(int i=0;i<weight;i++){
                serverList.add(server);
            }
        }

        Random random = new Random();
        int randomPos = random.nextInt(serverList.size());
        String server = serverList.get(randomPos);

        return server;
    }
}