Katama hash 是經常在分布式解決方案中見到的演算法,網上已經有很多文章介紹這個演算法或者其他的hash一致性演算法
前一陣子正好在做一個分布式系統的時候需要實現該演算法,在網上找了找,發現用C#實現的都不是很好。。
有一個搜尋出來結果最前面最多的實現,效能沒有最佳化過,代碼可讀性也不是很好。。
然後各個C#的memcached library中的實現又耦合的太緊了,所以自己搞了下面的這段代碼(參考了這位朋友的實現 http://www.cnblogs.com/daizhj/archive/2010/08/24/1807324.html)還有Beit的實現
using System;using System.Collections.Generic;using System.Text;using System.Security.Cryptography;namespace Clover{ public sealed class KetamaHash { private int[] Values = null; private string[] Nodes = null; public KetamaHash(IEnumerable<string> nodes, int copyNodes = 10000) { Refresh(nodes, copyNodes); } /// <summary> /// 該方法不是安全執行緒的,不過這個方法應該是很少調用的,只有在伺服器列表變更的時候才需要調用該方法 /// </summary> /// <param name="nodes"></param> /// <param name="copyNodes"></param> public void Refresh(IEnumerable<string> nodes, int copyNodes = 10000) { if (nodes == null) { throw new ArgumentNullException("nodes"); } if (copyNodes <= 0) { throw new ArgumentOutOfRangeException("virualNodes"); } SortedList<int, string> dict = new SortedList<int, string>(); HashSet<string> sortedNodes = new HashSet<string>(); foreach (var item in nodes) { if (item != null) sortedNodes.Add(item); } if ((sortedNodes.Count * copyNodes) > 320 * 1000) { throw new ArgumentOutOfRangeException("There is too many copyNodes or real nodes! nodes.Count multiply copyNodes must be not greater than 320*1000 "); } foreach (var node in sortedNodes) { for (int i = 0; i < copyNodes / 4; i++) { byte[] digest = Hash(node + "_" + i); for (int h = 0; h < 4; h++) { int m = BitConverter.ToInt32(digest, 0 * 4); dict[m] = node; } } } var newValues = new int[dict.Keys.Count]; var newNodes = new string[dict.Keys.Count]; dict.Keys.CopyTo(newValues, 0); dict.Values.CopyTo(newNodes, 0); Values = newValues; // thread not safty Nodes = newNodes; // thread not safty } public string GetNodeByKey(string key) { int value = BitConverter.ToInt32(Hash(key), 0); //first 4 byte to int32 int result = Array.BinarySearch<int>(Values, value); if (result < 0) result = ~result; if (result >= Nodes.Length) return Nodes[Nodes.Length - 1]; else return Nodes[result]; } #region Private Supported Method private byte[] Hash(byte[] source) { HashAlgorithm helper = new MD5CryptoServiceProvider(); return helper.ComputeHash(source); } private byte[] Hash(string s) { return Hash(Encoding.UTF8.GetBytes(s)); } #endregion }}
測試代碼如下:
static void Main(string[] args) { List<string> nodes = new List<string>(); for (int i = 0; i < 16; i++) { nodes.Add(Guid.NewGuid().ToString());//用來做測試代碼。。。。的隨機值 } KetamaHash target = new KetamaHash(nodes, 10000); Dictionary<string, int> dict = new Dictionary<string, int>(); Stopwatch sw = new Stopwatch(); sw.Start(); for (int i = 0; i < 1000 * 1000; i++)//運行一百萬次 { var result = target.GetNodeByKey(Guid.NewGuid().ToString());//用來做測試代碼。。。。的隨機值 if (result == null) { throw new Exception("沒取到資料"); } if (dict.ContainsKey(result)) { dict[result]++; } else { dict[result] = 1; } } sw.Stop(); long maxNumber = dict.Values.Max(); long minNumber = dict.Values.Min(); double temp = (maxNumber - minNumber) / Convert.ToDouble(maxNumber); Console.WriteLine(temp); Console.WriteLine(sw.ElapsedMilliseconds); if (temp >= 0.1) { Console.WriteLine("資料分布不均勻,嘗試增加虛擬節點會更均勻點"); } if (sw.ElapsedMilliseconds >= 12 * 1000) { Console.WriteLine("跑的太慢....當然 也有可能是你的機器太爛。。。。哈哈~"); } }
虛擬節點越多,資料分配越均勻,不過效能也相對差一點, 這邊推薦使用10000,分配會比較均勻,速度也不慢
經過效能測試 95% 以上的效能消耗在MD5演算法中,
如果換掉MD5的hash演算法效能會好點。。。。