一致性Hash演算法在Memcached中的應用

来源:http://www.cnblogs.com/shouce/archive/2016/01/15/5132223.html
-Advertisement-
Play Games

前言 大家應該都知道Memcached要想實現分散式只能在客戶端來完成,目前比較流行的是通過一致性hash演算法來實現.常規的方法是將server的hash值與server的總台數進行求餘,即hash%N,這種方法的弊端是當增減伺服器時,將會有較多的緩存需要被重新分配且會造成緩存分配不均勻的情況(有....


前言

  大家應該都知道Memcached要想實現分散式只能在客戶端來完成,目前比較流行的是通過一致性hash演算法來實現.常規的方法是將server的hash值與server的總台數進行求餘,即hash%N,這種方法的弊端是當增減伺服器時,將會有較多的緩存需要被重新分配且會造成緩存分配不均勻的情況(有可能某一臺伺服器分配的很多,其它的卻很少).

   今天分享一種叫做”ketama”的一致性hash演算法,它通過虛擬節點的概念和不同的緩存分配規則有效的抑制了緩存分佈不均勻,並最大限度地減少伺服器增減時緩存的重新分佈。 

實現思路

  假設我們現在有N台Memcached的Server,如果我們用統一的規則對memcached進行Set,Get操作. 使具有不同key的object很均衡的分散存儲在這些Server上,Get操作時也是按同樣規則去對應的Server上取出object,這樣各個Server之間不就是一個整體了嗎?

那到底是一個什麼樣的規則?

  如下圖所示,我們現在有5台(A,B,C,D,E)Memcached的Server,我們將其串聯起來形成一個環形,每一臺Server都代表圓環上的一個點,每一個點都具有唯一的Hash值,這個圓環上一共有2^32個點.

       那麼該如何確定每台Server具體分佈在哪個點上? 這裡我們通過”Ketama”的Hash演算法來計算出每台Server的Hash值,拿到Hash值後就可以對應到圓環上點了.(可以用Server的IP地址作為Hash演算法的Key.)

  這樣做的好處是,如下圖當我新增Server  F時,那麼我只需要將hash值落在C和F之間的object從原本的D上重新分配到F上就可以了,其它的server上的緩存不需要重新分配,並且新增的Server也能及時幫忙緩衝其它Server的壓力.

  到此我們已經解決了增減伺服器時大量緩存需要被重新分配的弊端.那該如何解決緩存分配不均勻的問題呢?因為現在我們的server只占據圓環上的6個點,而圓環上總共有2^32個點,這極其容易導致某一臺server上熱點非常多,某一臺上熱點很少的情況.

  ”虛擬節點”的概念很好的解決了這種負載不均衡的問題.通過將每台物理存在的Server分割成N個虛擬的Server節點(N通常根據物理Server個數來定,這裡有個比較好的閾值為250).這樣每個物理Server實際上對應了N個虛擬的節點. 存儲點多了,各個Server的負載自然要均衡一些.就像地鐵站出口一樣,出口越多,每個出口出現擁擠的情況就會越少.

   代碼實現:

複製代碼
//保存所有虛擬節點信息, key : 虛擬節點的hash key, value: 虛擬節點對應的真實server
        private Dictionary<uint, string> hostDictionary = new Dictionary<uint, string>();
        //保存所有虛擬節點的hash key, 已按升序排序
        private uint[] ketamaHashKeys = new uint[] { };
        //保存真實server主機地址
        private string[] realHostArr = new string[] { };
        //每台真實server對應虛擬節點個數
        private int VirtualNodeNum = 250;

        public KetamaVirtualNodeInit(string[] hostArr)
        {
            this.realHostArr = hostArr;
            this.InitVirtualNodes();
        }

        /// <summary>
        /// 初始化虛擬節點
        /// </summary>
        private void InitVirtualNodes()
        {
            hostDictionary = new Dictionary<uint, string>();
            List<uint> hostKeys = new List<uint>();
            if (realHostArr == null || realHostArr.Length == 0)
            {
                throw new Exception("不能傳入空的Server集合");
            }

            for (int i = 0; i < realHostArr.Length; i++)
            {
                for (int j = 0; j < VirtualNodeNum; j++)
                {
                    byte[] nameBytes = Encoding.UTF8.GetBytes(string.Format("{0}-node{1}", realHostArr[i], j));
                    //調用Ketama hash演算法獲取hash key
                    uint hashKey = BitConverter.ToUInt32(new KetamaHash().ComputeHash(nameBytes), 0);
                    hostKeys.Add(hashKey);
                    if (hostDictionary.ContainsKey(hashKey))
                    {
                        throw new Exception("創建虛擬節點時發現相同hash key,請檢查是否傳入了相同Server");
                    }
                    hostDictionary.Add(hashKey, realHostArr[i]);
                }
            }

            hostKeys.Sort();
            ketamaHashKeys = hostKeys.ToArray();
        }
複製代碼

 

一致性hash演算法的分配規則

  到此我們已經知道了所有虛擬節點的Hash值, 現在讓我們來看下當我們拿到一個對象時如何存入Server, 或是拿到一個對象的Key時該如何取出對象. 

       Set一個對象時,先將對象的Key作為”Ketama”演算法的Key,計算出Hash值後我們需要做下麵幾個步驟.

       1:首先檢查虛擬節點當中是否有與當前對象Hash值相等的,如有則直接將對象存入那個Hash值相等的節點,後面的步驟就不繼續了.

       2:如沒有,則找出第一個比當前對象Hash值要大的節點,(節點的Hash值按升序進行排序,圓環上對應按照順時針來排列),即離對象最近的節點,然後將對象存入該節點.

       3:如果沒有找到Hash值比對象要大的Server,證明對象的Hash值是介於最後一個節點和第一個節點之間的,也就是圓環上的E和A之間.這種情況就直接將對象存入第一個節點,即A.

  代碼實現:  

複製代碼
     /// <summary>
        /// 根據hash key 獲取對應的真實Server
        /// </summary>
        /// <param name="hash"></param>
        /// <returns></returns>
        public string GetHostByHashKey(string key)
        {
            byte[] bytes = Encoding.UTF8.GetBytes(key);
            uint hash = BitConverter.ToUInt32(new KetamaHash().ComputeHash(bytes), 0);

            //尋找與當前hash值相等的Server. 
            int i = Array.BinarySearch(ketamaHashKeys, hash);

            //如果i小於零則表示沒有hash值相等的虛擬節點
            if (i < 0)
            {
                //將i繼續按位求補,得到數組中第一個大於當前hash值的虛擬節點
                i = ~i;

                //如果按位求補後的i大於等於數組的大小,則表示數組中沒有大於當前hash值的虛擬節點
                //此時直接取第一個server
                if (i >= ketamaHashKeys.Length)
                {
                    i = 0;
                }
            }

            //根據虛擬節點的hash key 返回對應的真實server host地址
            return hostDictionary[ketamaHashKeys[i]];
        }
複製代碼

 

Get一個對象,同樣也是通過”Ketama”演算法計算出Hash值,然後與Set過程一樣尋找節點,找到之後直接取出對象即可.

那麼這個”Ketama”到底長什麼樣呢,讓我們來看看代碼實現.

複製代碼
    /// <summary>
    ///  Ketama hash加密演算法
    ///  關於HashAlgorithm參見MSDN鏈接
    ///  http://msdn.microsoft.com/zh-cn/library/system.security.cryptography.hashalgorithm%28v=vs.110%29.aspx
    /// </summary>
    public class KetamaHash : HashAlgorithm
    {

        private static readonly uint FNV_prime = 16777619;
        private static readonly uint offset_basis = 2166136261;

        protected uint hash;

        public KetamaHash()
        {
            HashSizeValue = 32;
        }

        public override void Initialize()
        {
            hash = offset_basis;
        }

        protected override void HashCore(byte[] array, int ibStart, int cbSize)
        {
            int length = ibStart + cbSize;
            for (int i = ibStart; i < length; i++)
            {
                hash = (hash * FNV_prime) ^ array[i];
            }
        }

        protected override byte[] HashFinal()
        {
            hash += hash << 13;
            hash ^= hash >> 7;
            hash += hash << 3;
            hash ^= hash >> 17;
            hash += hash << 5;
            return BitConverter.GetBytes(hash);
        }
    }
複製代碼

測試性能

最後我把自己參考BeitMemcached寫的演算法與老代(Discuz!代震軍)參考SPYMemcached寫的做了一下對比.

源碼在後面有下載.

結果:查找5W個key的時間比老代的版本快了100多倍,但在負載均衡方面差了一些. 

測試數據:

   1:真實Server都是5台

       2:隨機生成5W個字元串key(生成方法直接拿老代的)

       3:虛擬節點都是250個 

       我的版本:

老代的版本:

 


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

-Advertisement-
Play Games
更多相關文章
  • 命令模式的定義:將“請求”封裝成對象,以便使用不同的請求、隊列或者日誌來參數化其他對象。命令模式也支持撤銷的操作。註意命令模式是將請求封裝成對象! 其實簡單的說,命令模式就是把方法調用封裝起來了,通過封裝方法調用,可以把運算塊包裝成型,所以調用此運算的對象不需要關心事情是如何進行的,...
  • 1、SwipeBackLayout項目地址:https://github.com/ikew0ng/SwipeBackLayout2、用法android studiocompile 'me.imid.swipebacklayout.lib:library:1.0.0'項目實例package com.e...
  • 什麼是looksalive check和is alive check SQL Server故障轉移集群是建立在windows集群服務上的一種熱備的高可用方案。在集群運行過程中,windows集群服務定期檢測節點的資源健康狀態,如果發生了故障,會根據預先定義的故障轉移策略把SQL Server服務從....
  • 一、什麼是觸發器簡單的說,就是一張表發生了某件事(插入、刪除、更新操作),然後自動觸發了預先編寫好的若幹條SQL語句的執行;二、特點及作用特點:觸發事件的操作和觸發器里的SQL語句是一個事務操作,具有原子性,要麼全部執行,要麼都不執行;作用:保證數據的完整性,起到約束的作用;三、例子:創建觸發器,記...
  • 1 create procedure usp_CallWebServices 2 ( 3 @parameter nvarchar(500)=null 4 ) 5 as 6 Declare @obj int 7 Declare @SvercieUrl nvarchar(200) 8 ...
  • 流程式控制制 1.If,then,else,elsif(不是elseif) if a='1' then null; endif; 2.Case 簡單case表達式: 搜索型Case表達式: 3.goto語句 begin if true then goto label2; end if; > SYS.DB...
  • 剛逛論壇,發現一個這樣的問題,如果不建立一個新的月份的表,可以用CET來解決。給定一張表(列有月份,銷售額),要求查詢出月份、本月銷售額、上月銷售額這三個結果,如果當月上個月的銷售額不存在就顯示為“*”。if exists (select * from sysobjects where id = o...
  • 工作和學習中常常會遇到一行要分割成多行數據的情況,在此整理一下做下對比。 單行拆分 如果表數據只有一行,則可以直接在原表上直接使用connect by+正則的方法,比如: select regexp_substr('444.555.666', '[^.]+', 1, level) col from ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...