一篇有趣的負載均衡算法實現_網頁設計公司

1{icon} {views}

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

文章已經收錄在 Github.com/niumoo/JavaNotes ,更有 Java 程序員所需要掌握的核心知識,歡迎Star和指教。
歡迎關注我的公眾號,文章每周更新。

負載平衡(Load balancing)是一種在多個計算機(網絡、CPU、磁盤)之間均勻分配資源,以提高資源利用的技術。使用負載均衡可以最大化服務吞吐量,可能最小化響應時間,同時由於使用負載均衡時,會使用多個服務器節點代單點服務,也提高了服務的可用性。

負載均衡的實現可以軟件可以硬件,硬件如大名鼎鼎的 F5 負載均衡設備,軟件如 NGINX 中的負載均衡實現,又如 Springcloud Ribbon 組件中的負載均衡實現。

如果看到這裏你還不知道負載均衡是幹嘛的,那麼只能放一張圖了,畢竟沒圖說個啥。

負載均衡要做到在多次請求下,每台服務器被請求的次數大致相同。但是實際生產中,可能每台機器的性能不同,我們會希望性能好的機器承擔的請求更多一些,這也是正常需求。

如果這樣說下來你看不懂,那我就再舉個例子好了,一排可愛的小熊(服務器)站好。

這時有人(用戶)要過來打臉(請求訪問)。

那麼怎麼樣我們才能讓這每一個可愛的小熊被打的次數大致相同呢?

又或者熊 4 比較胖,抗擊打能力是別人的兩倍,我們怎麼提高熊 4 被打的次數也是別人的兩倍呢?

又或者每次出手的力度不同,有重有輕,恰巧熊 4 總是承受這種大力度啪啪打臉,熊 4 即將不省熊事,還要繼續打它嗎?

這些都是值的思考的問題。

說了那麼多,口乾舌燥,我雙手已經饑渴難耐了,迫不及待的想要擼起代碼了。

1. 隨機訪問

上面說了,為了負載均衡,我們必須保證多次出手后,熊 1 到熊 4 被打次數均衡。比如使用隨機訪問法,根據數學上的概率論,隨機出手次數越多,每隻熊被打的次數就會越相近。代碼實現也比較簡單,使用一個隨機數,隨機訪問一個就可以了。

/** 服務器列表 */
private static List<String> serverList = new ArrayList<>();
static {
    serverList.add("192.168.1.2");
    serverList.add("192.168.1.3");
    serverList.add("192.168.1.4");
    serverList.add("192.168.1.5");
}

/**
 * 隨機路由算法
 */
public static String random() {
    // 複製遍歷用的集合,防止操作中集合有變更
    List<String> tempList = new ArrayList<>(serverList.size());
    tempList.addAll(serverList);
    // 隨機數隨機訪問
    int randomInt = new Random().nextInt(tempList.size());
    return tempList.get(randomInt);
}

因為使用了非線程安全的集合,所以在訪問操作時操作的是集合的拷貝,下面幾種輪詢方式中也是這種思想。

寫一個模擬請求方法,請求10w次,記錄請求結果。

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

public static void main(String[] args) {
    HashMap<String, Integer> serverMap = new HashMap<>();
    for (int i = 0; i < 20000; i++) {
        String server = random();
        Integer count = serverMap.get(server);
        if (count == null) {
            count = 1;
        } else {
            count++;
        }
        // 記錄
        serverMap.put(server, count);
    }
    // 路由總體結果
    for (Map.Entry<String, Integer> entry : serverMap.entrySet()) {
        System.out.println("IP:" + entry.getKey() + ",次數:" + entry.getValue());
    }
}

運行得到請求結果。

IP:192.168.1.3,次數:24979
IP:192.168.1.2,次數:24896
IP:192.168.1.5,次數:25043
IP:192.168.1.4,次數:25082

每台服務器被訪問的次數都趨近於 2.5w,有點負載均衡的意思。但是隨機畢竟是隨機,是不能保證訪問次數絕對均勻的。

2. 輪詢訪問

輪詢訪問就簡單多了,拿上面的熊1到熊4來說,我們一個接一個的啪啪 – 打臉,熊1打完打熊2,熊2打完打熊3,熊4打完打熊1,最終也是實現了被打均衡。但是保證均勻總是要付出代價的,隨機訪問中需要隨機,輪詢訪問中需要什麼來保證輪詢呢?

/** 服務器列表 */
private static List<String> serverList = new ArrayList<>();
static {
    serverList.add("192.168.1.2");
    serverList.add("192.168.1.3");
    serverList.add("192.168.1.4");
    serverList.add("192.168.1.5");
}
private static Integer index = 0;

/**
 * 隨機路由算法
 */
public static String randomOneByOne() {
    // 複製遍歷用的集合,防止操作中集合有變更
    List<String> tempList = new ArrayList<>(serverList.size());
    tempList.addAll(serverList);
    String server = "";
    synchronized (index) {
        index++;
        if (index == tempList.size()) {
            index = 0;
        }
        server = tempList.get(index);;
    }
    return server;
}

由代碼里可以看出來,為了保證輪詢,必須記錄上次訪問的位置,為了讓在併發情況下不出現問題,還必須在使用位置記錄時進行加鎖,很明顯這種互斥鎖增加了性能開銷。

依舊使用上面的測試代碼測試10w次請求負載情況。

IP:192.168.1.3,次數:25000
IP:192.168.1.2,次數:25000
IP:192.168.1.5,次數:25000
IP:192.168.1.4,次數:25000

3. 輪詢加權

上面演示了輪詢方式,還記的一開始提出的熊4比較胖抗擊打能力強,可以承受別人2倍的挨打次數嘛?上面兩種方式都沒有體現出來熊 4 的這個特點,熊 4 竊喜,不痛不癢。但是熊 1 到 熊 3 已經在崩潰的邊緣,不行,我們必須要讓胖着多打,能者多勞,提高整體性能。

/** 服務器列表 */
private static HashMap<String, Integer> serverMap = new HashMap<>();
static {
    serverMap.put("192.168.1.2", 2);
    serverMap.put("192.168.1.3", 2);
    serverMap.put("192.168.1.4", 2);
    serverMap.put("192.168.1.5", 4);
}
private static Integer index = 0;

/**
 * 加權路由算法
 */
public static String oneByOneWithWeight() {
    List<String> tempList = new ArrayList();
    HashMap<String, Integer> tempMap = new HashMap<>();
    tempMap.putAll(serverMap);
    for (String key : serverMap.keySet()) {
        for (int i = 0; i < serverMap.get(key); i++) {
            tempList.add(key);
        }
    }
    synchronized (index) {
        index++;
        if (index == tempList.size()) {
            index = 0;
        }
        return tempList.get(index);
    }
}

這次記錄下了每台服務器的整體性能,給出一個數值,數值越大,性能越好。可以承受的請求也就越多,可以看到服務器 192.168.1.5 的性能為 4,是其他服務器的兩倍,依舊 10 w 請求測試。

IP:192.168.1.3,次數:20000
IP:192.168.1.2,次數:20000
IP:192.168.1.5,次數:40000
IP:192.168.1.4,次數:20000

192.168.1.5 承擔了 2 倍的請求。

4. 隨機加權

隨機加權的方式和輪詢加權的方式大致相同,只是把使用互斥鎖輪詢的方式換成了隨機訪問,按照概率論來說,訪問量增多時,服務訪問也會達到負載均衡。

/** 服務器列表 */
private static HashMap<String, Integer> serverMap = new HashMap<>();
static {
    serverMap.put("192.168.1.2", 2);
    serverMap.put("192.168.1.3", 2);
    serverMap.put("192.168.1.4", 2);
    serverMap.put("192.168.1.5", 4);
}
/**
 * 加權路由算法
 */
public static String randomWithWeight() {
    List<String> tempList = new ArrayList();
    HashMap<String, Integer> tempMap = new HashMap<>();
    tempMap.putAll(serverMap);
    for (String key : serverMap.keySet()) {
        for (int i = 0; i < serverMap.get(key); i++) {
            tempList.add(key);
        }
    }
    int randomInt = new Random().nextInt(tempList.size());
    return tempList.get(randomInt);
}

依舊 10 w 請求測試,192.168.1.5 的權重是其他服務器的近似兩倍,

IP:192.168.1.3,次數:19934
IP:192.168.1.2,次數:20033
IP:192.168.1.5,次數:39900
IP:192.168.1.4,次數:20133

5. IP-Hash

上面的幾種方式要麼使用隨機數,要麼使用輪詢,最終都達到了請求的負載均衡。但是也有一個很明顯的缺點,就是同一個用戶的多次請求很有可能不是同一個服務進行處理的,這時問題來了,如果你的服務依賴於 session ,那麼因為服務不同, session 也會丟失,不是我們想要的,所以出現了一種根據請求端的 ip 進行哈希計算來決定請求到哪一台服務器的方式。這種方式可以保證同一個用戶的請求落在同一個服務上。

private static List<String> serverList = new ArrayList<>();
static {
    serverList.add("192.168.1.2");
    serverList.add("192.168.1.3");
    serverList.add("192.168.1.4");
    serverList.add("192.168.1.5");
}

/**
 * ip hash 路由算法
 */
public static String ipHash(String ip) {
    // 複製遍歷用的集合,防止操作中集合有變更
    List<String> tempList = new ArrayList<>(serverList.size());
    tempList.addAll(serverList);
    // 哈希計算請求的服務器
    int index = ip.hashCode() % serverList.size();
    return tempList.get(Math.abs(index));
}

6. 總結

上面的四種方式看似不錯,那麼這樣操作下來真的體現了一開始說的負載均衡嗎?答案是不一定的。就像上面的最後一個提問。

又或者每次出手的力度不同,有重有輕,恰巧熊 4 總是承受這種大力度啪啪打臉,熊 4 即將不省熊事,還要繼續打它嗎?

服務器也是這個道理,每次請求進行的操作對資源的消耗可能是不同的。比如說某些操作它對 CPU 的使用就是比較高,也很正常。所以負載均衡有時不能簡單的通過請求的負載來作為負載均衡的唯一依據。還可以結合服務的當前連接數量、最近響應時間等維度進行總體均衡,總而言之,就是為了達到資源使用的負載均衡。

最後的話

文章已經收錄在 Github.com/niumoo/JavaNotes ,歡迎Star和指教。更有一線大廠面試點,Java程序員需要掌握的核心知識等文章,也整理了很多我的文字,歡迎 Star 和完善,希望我們一起變得優秀。

文章有幫助可以點個「」或「分享」,都是支持,我都喜歡!
文章每周持續更新,要實時關注我更新的文章以及分享的乾貨,可以關注「 未讀代碼 」公眾號或者我的博客。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。