負載均衡演算法 - 基本實現

来源:https://www.cnblogs.com/anthony-dong/archive/2020/02/01/12250726.html
-Advertisement-
Play Games

最近在比賽一個項目 , 是給Dubbo寫一個負載均衡介面 , 其實dubbo已經實現了下麵四種, 所以他做的不是這個單面負載均衡, 需要做雙向負載均衡 , 負載均衡的權重取決於服務端,所以有些時候我們不知道如何計算權重, 權重受到很多因素影響 ,所以就需要動態考慮了. ...


​ 最近在比賽一個項目 , 是給Dubbo寫一個負載均衡介面 , 其實dubbo已經實現了下麵四種, 所以他做的不是這個單面負載均衡, 需要做雙向負載均衡 , 負載均衡的權重取決於服務端,所以有些時候我們不知道如何計算權重, 權重受到很多因素影響 ,所以就需要動態考慮了.

​ Dubbo 提供了4種負載均衡實現,分別是基於權重隨機演算法的 RandomLoadBalance、基於最少活躍調用數演算法的 LeastActiveLoadBalance、基於 hash 一致性的 ConsistentHashLoadBalance,以及基於加權輪詢演算法的 RoundRobinLoadBalance。

1. RandomLoadBalance

RandomLoadBalance 是加權隨機演算法的具體實現,它的演算法思想很簡單。假設我們有一組伺服器 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次。

private <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {

    int len = invokers.size();
    int[] arr = new int[len];
    int count = 0;
    int totalWight = 0;
    for (Invoker<T> invoker : invokers) {
        int wight = invoker.getUrl().getParameter(org.apache.dubbo.common.Constants.WEIGHT_KEY, 1);
        arr[count++] = wight;
        totalWight += wight;
    }

    // 隨機偏移量
    int offset = ThreadLocalRandom.current().nextInt(totalWight);

    for (int i = 0; i < arr.length; i++) {

        // 比如 [2 , 3 , 5]  , offset=7 , 此時 offset-2=5 , offset-3=2 , offset-5=-3 ,此時當他為負數時,說明就在這個區間內, 我們是左閉右開[0,2) ,[2,5) ,[5,10)
        offset -= arr[i];

        // 此時已經小於0了, 說明在這個區間內
        if (offset < 0) {
            // 返回就行了
            return invokers.get(i);
        }
    }

    return ... 
}

2. LeastActiveLoadBalance

LeastActiveLoadBalance 翻譯過來是最小活躍數負載均衡。活躍調用數越小,表明該服務提供者效率越高,單位時間內可處理更多的請求。此時應優先將請求分配給該服務提供者。在具體實現中,每個服務提供者對應一個活躍數 active。初始情況下,所有服務提供者活躍數均為0。每收到一個請求,活躍數加1,完成請求後則將活躍數減1。在服務運行一段時間後,性能好的服務提供者處理請求的速度更快,因此活躍數下降的也越快,此時這樣的服務提供者能夠優先獲取到新的服務請求、這就是最小活躍數負載均衡演算法的基本思想。除了最小活躍數,LeastActiveLoadBalance 在實現上還引入了權重值。所以準確的來說,LeastActiveLoadBalance 是基於加權最小活躍數演算法實現的。舉個例子說明一下,在一個服務提供者集群中,有兩個性能優異的服務提供者。某一時刻它們的活躍數相同,此時 Dubbo 會根據它們的權重去分配請求,權重越大,獲取到新請求的概率就越大。如果兩個服務提供者權重相同,此時隨機選擇一個即可。

​ 這個需要我們去維護一個最小連接數的計算, 配合加權 ,當連接數相同的時候,選擇加權分最高的 ...

3. ConsistentHashLoadBalance

一致性 hash 演算法由麻省理工學院的 Karger 及其合作者於1997年提出的,演算法提出之初是用於大規模緩存系統的負載均衡。它的工作過程是這樣的,首先根據 ip 或者其他的信息為緩存節點生成一個 hash,並將這個 hash 投射到 [0, 232 - 1] 的圓環上。當有查詢或寫入請求時,則為緩存項的 key 生成一個 hash 值。然後查找第一個大於或等於該 hash 值的緩存節點,併到這個節點中查詢或寫入緩存項。如果當前節點掛了,則在下一次查詢或寫入緩存時,為緩存項查找另一個大於其 hash 值的緩存節點即可。

​ 這個主要是靠hash演算法 , 通過hash % 伺服器數量 = 伺服器索引

​ Java中可以使用這個 : int identityHashCode = System.identityHashCode(invokers);來獲取

4. RoundRobinLoadBalance

加權輪詢負載均衡的實現 RoundRobinLoadBalance , 我選擇的就是這種

這裡從最簡單的輪詢開始講起,所謂輪詢是指將請求輪流分配給每台伺服器。舉個例子,我們有三台伺服器 A、B、C。我們將第一個請求分配給伺服器 A,第二個請求分配給伺服器 B,第三個請求分配給伺服器 C,第四個請求再次分配給伺服器 A。這個過程就叫做輪詢。輪詢是一種無狀態負載均衡演算法,實現簡單,適用於每台伺服器性能相近的場景下。但現實情況下,我們並不能保證每台伺服器性能均相近。如果我們將等量的請求分配給性能較差的伺服器,這顯然是不合理的。

因此,這個時候我們需要對輪詢過程進行加權,以調控每台伺服器的負載。經過加權後,每台伺服器能夠得到的請求數比例,接近或等於他們的權重比。比如伺服器 A、B、C 權重比為 5:2:1。那麼在8次請求中,伺服器 A 將收到其中的5次請求,伺服器 B 會收到其中的2次請求,伺服器 C 則收到其中的1次請求。

下麵有個表格 , 預設權重是 [5,1,1]

請求編號 currentWeight 數組 選擇結果 減去權重總和後的 currentWeight 數組
1 [5, 1, 1] A [-2, 1, 1]
2 [3, 2, 2] A [-4, 2, 2]
3 [1, 3, 3] B [1, -4, 3]
4 [6, -3, 4] A [-1, -3, 4]
5 [4, -2, 5] C [4, -2, -2]
6 [9, -1, -1] A [2, -1, -1]
7 [7, 0, 0] A [0, 0, 0]

此時 7 次中A節點被選中的次數是 5 , B 是1 ,C是1 ,所以符合我們的需求

計算方法如下 , 首先有三個變數 , 記錄了 currentWeight , effectiveWeight , totalWeight

 private class Node {
        private int currentWeight; // 當前權重
        private int effectiveWeight; // 有效權重,初始化的時候等於當前權重
        private int totalWeight; // 總權重
 }     

我們先看初始化 , 初始化時 ,計算權重

比如我們知道權重了 A : 5 , B : 1 , C : 1

初始化節點

A : new Node(5, 5, 7)
B : new Node(1, 1, 7)
C : new Node(1, 1, 7)
    
此時currentWeight的和是 : 7    

當第一次的時候 , A節點權重最大 ,此時 5-7=-2, A節點變成了 Node(-2, 5, 7)

A : new Node(-2, 5, 7)
B : new Node(1, 1, 7)
C : new Node(1, 1, 7)

此時currentWeight的和是 : 0

然後重新回歸, 回歸需要currentWeight= currentWeight+effectiveWeight

A : new Node(3, 5, 7)
B : new Node(2, 1, 7)
C : new Node(2, 1, 7)

此時currentWeight的和是 : 7 // 所以又回來了 .. .. 

怎樣做呢 ?

此時我們用一個 PriorityQueue<Pair<String, Node>> 維護所有節點的信息 , 同時使用 Pair<String, Node>維護單個節點

1. 初始化隊列
PriorityBlockingQueue<Pair<String, Node>> weightQueue = new PriorityBlockingQueue<>(3, (o1, o2) -> o2.getValue().getCurrentWeight() - o1.getValue().getCurrentWeight());


2. 遍歷放入權重
weightQueue.add(new Pair<>(key, new Node(currentWeight, effectiveWeight, totalWeight))); 

3. 當放入以後, 此時就可以拿到一個當前權重最大的節點 , 如果不想使用優先隊列, 可以自己實現一個大頂堆 ,很簡單的. 
    
// 獲取當前節點權重最大的 ,     
Pair<String, Node> hPair = weightQueue.take(); 
// 此時hPair節點的值應當是 5 - 7 = -2 
int afterCurrentWeight = value.getCurrentWeight() - value.getTotalWeight();
// 設置值
hNode.setCurrentWeight(afterCurrentWeight);

// 遍歷
for (Pair<String, Node> stringNodePair : weightQueue) {
    //獲取節點 
    Node node = stringNodePair.getValue();
    // 剩餘每個節點值 + 有效值 
    int after = node.getCurrentWeight() + node.getEffectiveWeight();
    
    // 設置節點值 
    node.setCurrentWeight(after);
}
// 由於我們拿出來的節點還沒有 重新計算, 還要計算
hNode.setCurrentWeight(value.getCurrentWeight() + value.getEffectiveWeight());
// 重新放入節點. ... ,重新排序
weightQueue.add(hPair);

其實自己維護的話可以進行 heapfy的 , 我們只能依賴JDK提供的數據結構進行的這種取巧方式


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 計算屬性關鍵詞: computed demo1: <div id="app"> <p>原始字元串: {{ message }}</p> <p>計算後反轉字元串: {{ reversedMessage }}</p> </div> <script> var vm = new Vue({ el: '#ap ...
  • 常見行佈局: 導航使用position:fixed固定住 導航會脫離文檔流,不占據空間 導致下麵的元素上移,因此需要將下麵的元素的padding-top設置成導航的高度 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <t ...
  • Umi 通常會搭配 Dva 使用,用於管理頁面狀態和邏輯 一、註冊 model 首先需要在 .umirc.js 中啟用 dva 插件 export default { plugins: [ ['umi-plugin-react', { dva: { immer: true, }, }], ], } ...
  • jQuery事件發展歷程 事件發展歷程:從簡單事件,到bind,到委托事件,到on事件綁定 //簡單事件,給自己註冊的事件 $("div").click(function () { alert("哈哈"); }); //bind方式 $("p").bind({ click: function () ...
  • 最近做個小項目,給網頁加個浮窗,考驗了基礎的css,js技術,還是蠻有意思的,代碼如下(部分代碼來源於引用,見底部) <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=d ...
  • 前端的性能優化 資源的壓縮與合併 + 優化要點:減少http數量請求和資源大小請求 + 運用壓縮與合併 + 實現方式有線上網站和壓縮工具(需要node) web前端本質上是一種GUI軟體,本可以直接借鑒其他GUI系統架構設計方法,但web前端有點特別 瀏覽器的一個請求從發送到返回都經歷了什麼? 在這 ...
  • 一、前言 斷斷續續的也有在閑餘時間接觸領域驅動設計的相關知識,因為目前在工作中更多的還只是一名 crud boy,因此目前也只是對其中的某些知識點有知曉,實際使用的比較少,僅此而已。因此,趁著這個春節假期,整理了一下自己的 github 帳號,同時結合自己定的學習計劃以及自己的期望發展方向,決定從一 ...
  • Java基礎系列1:深入理解Java數據類型 當初學習電腦的時候,教科書中對程式的定義是:程式=數據結構+演算法,Java基礎系列第一篇就聊聊Java中的數據類型。 本篇聊Java數據類型主要包括四個內容: Java基本類型 Java封裝類型 自動裝箱和拆箱 封裝類型緩存機制 Java基本類型 Ja ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...