Katama hash 算法的C#實現
Katama hash 是經常在分布式解決方案中見到的算法,網上已經有很多文章介紹這個算法或者其他的hash一致性算法
前一陣子正好在做一個分布式系統的時候需要實現該算法,在網上找了找,發現用C#實現的都不是很好。。
有一個搜索出來結果最前面最多的實現,性能沒有優化過,代碼可讀性也不是很好。。
然后各個C#的memcached library中的實現又耦合的太緊了,所以自己搞了下面的這段代碼(參考了這位朋友的實現 http://www.rzrgm.cn/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算法性能會好點。。。。
浙公網安備 33010602011771號