C# 淺顯易懂搞明白,哈希一致性算法的原理與實現,

有圖有真相

原理:根據特定的字符串比如5臺服務器ip的數組,按照一定放大倍數比如4000倍,哈希映射出20000個哈希值,他們的關係存到ketamaNides數組中,每臺服務器,對應相同等份的4000個哈希值,

現在有一批量字符串數據,比如10000條要存進來,如何決定它存到哪臺服務器呢?

這個hash值和根據服務器ip映射出來的hash值,可以比較大小,

根據ketamaNodes找到距離最近的一個服務器ip映射出來的hash值,進而知道該服務器名稱,

重點是根據hash定義,不管它是什麼字符串,都可以映射成等長的hash值,

博主這種說法也許並不專業,但是絕對淺顯易懂,幫助理解是沒問題的!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

namespace HashSetDemo
{
    public class KetamaNodeLocator
    {

        private SortedList<long, string> ketamaNodes = new SortedList<long, string>();
        private HashAlgorithm hashAlg;
        private int numReps = 4000;
        public List<string> services;
        //每臺服務器負載,和具體數據沒多大關係,
        public int TestItem = 10000;
        public KetamaNodeLocator(List<string> nodes/*,int nodeCopies*/)
        {
            services = nodes;
            ketamaNodes = new SortedList<long, string>();
            //numReps = nodeCopies;
            //對所有節點,生成nCopies個虛擬結點
            //方便理解,就認爲有四個物理節點
            foreach (string node in nodes)
            {
                //每四個虛擬結點爲一組
                //40組,160節點
                for (int i = 0; i < numReps / 4; i++)
                {
                    //getKeyForNode方法爲這組虛擬結點得到惟一名稱 
                    //每一個物理節點,需要生成四十個虛擬節點
                    byte[] digest = HashAlgorithm.computeMd5(node + i);
                    /** Md5是一個16字節長度的數組,將16字節的數組每四個字節一組,分別對應一個虛擬結點,這就是爲什麼上面把虛擬結點四個劃分一組的原因*/
                    //具體給每一個虛擬節點生成一個哈希值,
                    //四十個組,每組再循環四組
                    for (int h = 0; h < 4; h++)
                    {
                        long m = HashAlgorithm.hash(digest, h);
                        ketamaNodes[m] = node;
                    }
                }
            }
            //關鍵關鍵的是,每個ketamaNodes存,hash值和每臺物理機器ip
            //只是爲何不更簡單點,直接A_1_1 A_1_2這樣劃分一百六十個虛擬節點呢
        }
        public string GetPrimary(string k)
        {
            byte[] digest = HashAlgorithm.computeMd5(k);
            string rv = GetNodeForKey(HashAlgorithm.hash(digest, 0));
            return rv;
        }

        public void AddServer(string nodename)
        {
            //循環muti次數,給一個名字,生成不同的hash值,即一個hash環
            //同一個名字,產生不同的hash值散佈關聯
            services.Add(nodename);
            //每四個虛擬結點爲一組
            //40組,160節點
            for (int i = 0; i < numReps / 4; i++)
            {
                //getKeyForNode方法爲這組虛擬結點得到惟一名稱 
                //每一個物理節點,需要生成四十個虛擬節點
                byte[] digest = HashAlgorithm.computeMd5(nodename + i);
                /** Md5是一個16字節長度的數組,將16字節的數組每四個字節一組,分別對應一個虛擬結點,這就是爲什麼上面把虛擬結點四個劃分一組的原因*/
                //具體給每一個虛擬節點生成一個哈希值,
                //四十個組,每組再循環四組
                for (int h = 0; h < 4; h++)
                {
                    long m = HashAlgorithm.hash(digest, h);
                    ketamaNodes[m] = nodename;
                }
            }
            Console.WriteLine("新增:" + nodename);
        }

        //刪除某臺服務器,刪除對應所有的hash
        public void RemoveServer(string nodename)
        {
           
            //每四個虛擬結點爲一組
            services.Remove(nodename);
            //40組,160節點
            for (int i = 0; i < numReps / 4; i++)
            {
                //getKeyForNode方法爲這組虛擬結點得到惟一名稱 
                //每一個物理節點,需要生成四十個虛擬節點
                byte[] digest = HashAlgorithm.computeMd5(nodename + i);
                /** Md5是一個16字節長度的數組,將16字節的數組每四個字節一組,分別對應一個虛擬結點,這就是爲什麼上面把虛擬結點四個劃分一組的原因*/
                //具體給每一個虛擬節點生成一個哈希值,
                //四十個組,每組再循環四組
                for (int h = 0; h < 4; h++)
                {
                    long m = HashAlgorithm.hash(digest, h);
                    ketamaNodes.Remove(m);
                }
            }
            Console.WriteLine("移除:"+nodename);
        }
        string GetNodeForKey(long hash)
        {
            string rv;
            long key = hash;
            //如果找到這個節點,直接取節點,返回   
            if (!ketamaNodes.ContainsKey(key))
            {

                var tailMap = from coll in ketamaNodes
                              where coll.Key > hash
                              select new { coll.Key };
                if (tailMap == null || tailMap.Count() == 0)
                    key = ketamaNodes.FirstOrDefault().Key;
                else
                    key = tailMap.FirstOrDefault().Key;
            }
            rv = ketamaNodes[key];
            return rv;
        }

        //打印消息
        public void Print()
        {
            Console.WriteLine($"當前服務器個數:{services.Count},放大倍數:{numReps},總節點數:{services.Count*numReps}");

            //單獨存放每臺服務器中添加數據條數,
            SortedList<string,int> data= new SortedList<string, int>();

            //存多少條數據進入,根據hash算法,存到那臺服務器,但是
            //160放大倍數,形成將近一百六十個節點,會不會數據過大,全部落在外面呢?即
            //有可能很多比最小值小,那麼放在第一臺數量將會很多,同理很多比最大值大,放在最後一臺數量要多
            for (int i = 0; i < TestItem; i++)
            {
                //!!!!!注意kelata裏面的hash都是通過以下兩步獲取對應的hash,後面nTime默認給0即可
                string vname=GetNodeForKey(HashAlgorithm.hash(HashAlgorithm.computeMd5($"我是要存的數據{i}"),0));
                //通過kelataNodes集合,找到數據,該存入的服務器名稱,
                //一定能找到麼?儘管這個值不一定等於已存在的某個key,不過仍然能找到大於這個hash的下一個key,所對應的服務器
                //,1-3-5,7-9-11,如果是8大於它就是9放在第二臺服務器
                if (data.ContainsKey(vname))
                {
                    data[vname] += 1;
                }
                else {
                    data[vname] = 1;
                }
            }
            //
            Console.WriteLine($"存入的數據條數:{TestItem}");
            foreach (var item in data)
            {
                Console.WriteLine($"{item.Key},承載數據{((float)item.Value/(float)TestItem*100).ToString("0.00")}%");
            }

    }
    }
    public class HashAlgorithm
    {
       //這是一種hash算法
        public static long hash(byte[] digest, int nTime)
        {
            long rv = ((long)(digest[3 + nTime * 4] & 0xFF) << 24)
                    | ((long)(digest[2 + nTime * 4] & 0xFF) << 16)
                    | ((long)(digest[1 + nTime * 4] & 0xFF) << 8)
                    | ((long)digest[0 + nTime * 4] & 0xFF);
            return rv & 0xffffffffL; /* Truncate to 32-bits */
        }
        /**
         * Get the md5 of the given key.
         */
        public static byte[] computeMd5(string k)
        {
            MD5 md5 = new MD5CryptoServiceProvider();

            byte[] keyBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(k));
            md5.Clear();
            //md5.update(keyBytes);
            //return md5.digest();
            return keyBytes;
        }
       
    }
    
}
 KetamaNodeLocator kn = new KetamaNodeLocator(new List<string>() { "我是1號服務器", "我是2號服務器", "我是3號服務器", "我是4號服務器", "我是5號服務器"});
            kn.Print();
            Console.WriteLine("\r\n");
            kn.AddServer("我是6號服務器");
            kn.Print();
            Console.WriteLine("\r\n");
            kn.RemoveServer("我是1號服務器");
            kn.Print();
            Console.ReadLine();

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章