《Shuttler.Net东写西读之二》Memcached的Consistent Hashing负载算法

“何时一登陟,万物皆下顾”--贾岛

首先欢迎suyang加入Shuttler.Net开发团队,队伍壮大了(2人)!

此片继续谈算法。

Shuttler.Net里的Memcached哈希定位是通过求余来算的,这就引发一个问题:如果我增加一台缓存主机,那岂不是糟糕了:可能有N/(N+1)的缓存数据需要重新计算!其实还有一种全世界都在用的算法:Consistent Hashing。传说在Memcached、Key-Value Store、Bittorrent DHT、LVS中都采用了它,可以说Consistent Hashing 是分布式系统负载均衡的首选算法。

此算法大体描述如下(以Memcached为例):

代码
由于hash算法结果一般为unsigned int型,因此对于hash函数的结果应该均匀分布在[0,2^32-1]间,如果我们把一个圆环用2^32 个点来进行均匀切割,首先按照hash(key)函数算出服务器(节点)的哈希值, 并将其分布到0~2^32的圆上。
用同样的hash(key)函数求出需要存储数据的键的哈希值,并映射到圆上。然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器(节点)上。

说的比较晦涩,那我转几副图:

 

 当新增一个节点(服务器)的时候,只有在圆环上新增节点逆时针方向的第一个节点的数据会受到影响(删除则在圆环上原节点顺时针方向的第一个节点的数据会受到影响),所以很好的避免了hash值的大起大落。

 

其实原理挺简单,但是确实解决问题。

不过此算法的缺点是:如果节点间的虚拟节点区间大小设置不好,会造成个别服务器压力过大。所以在设置前还是需要考虑下key的hash值模型。
overred就写了个C#版的Consistent Hashing,以备Shuttler.Net之用:

代码
public class  ConsistentHashing<TKey>
   {
      
private object _sync = new object();
      
private IEqualityComparer<TKey> _comparer;
      
private SortedDictionary<intstring> _dict;

      
public ConsistentHashing(int dummyNodeNum, IList<string> servers)
      {
         _comparer 
= EqualityComparer<TKey>.Default;
         _dict
=new SortedDictionary<int,string>();

         
int c=1;
         
foreach (string server in servers)
         {
            
int node = (c++* dummyNodeNum & 0x7fffffff;
            _dict.Add(node, server);
         }
      }

      
public void Remove(string server)
      {
         
lock (_sync)
         {
            
foreach (KeyValuePair<intstring> kv in _dict)
            {
               
if (kv.Value.Equals(server))
               {
                  _dict.Remove(kv.Key);
                  
return;
               }
            }
         }
      }

      
public void Add(int dummyNodeNum, string server)
      {
         dummyNodeNum 
= dummyNodeNum & 0x7fffffff;
         
lock(_sync) _dict.Add(dummyNodeNum, server);
      }

      
public string Get(TKey key)
      {
         
int keyHash = _comparer.GetHashCode(key) & 0x7fffffff;
         
lock (_sync)
         {
            
if (keyHash > _dict.Last().Key)
               
return _dict.First().Value;

            
return _dict.First(index => index.Key >= keyHash).Value;
         }
      }

      
public override string  ToString()
      {
         StringBuilder sb 
= new StringBuilder();
         
foreach (KeyValuePair<intstring> kv in _dict)
            sb.AppendFormat(
"start:{0},server:{1}\r\n",kv.Key,kv.Value);

         
return sb.ToString();
      }
   }

测试代码:

代码
 static void Main(string[] args)
      {
         List
<string> ss = new List<string>();
         ss.Add(
"127.0.0.1");
         ss.Add(
"127.0.0.2");
         ss.Add(
"127.0.0.3");
         ss.Add(
"127.0.0.4");

         ConsistentHashing
<string> hash = new ConsistentHashing<string>((int.MaxValue / 4), ss);
         ShowStatus(hash);

         hash.Add((
int.MaxValue/5),"127.0.0.5");
         ShowStatus(hash);

         hash.Remove(
"127.0.0.4");
         ShowStatus(hash);

         Console.Read();
      }

      
static void ShowStatus(ConsistentHashing<string> hash)
      {
         
for (int i = 0; i < 10; i++)
            Console.WriteLine(hash.Get(
"xx" + i.ToString()));
         Console.WriteLine(
"+++++++++++++++++++++++++++++");
         Console.WriteLine(hash.ToString());
         Console.WriteLine(
"+++++++++++++++++++++++++++++");

      }

测试结果:

代码
127.0.0.2
127.0.0.3
127.0.0.4
127.0.0.1
127.0.0.3
127.0.0.4
127.0.0.1
127.0.0.2
127.0.0.2
127.0.0.3
+++++++++++++++++++++++++++++
start:
536870911,server:127.0.0.1
start:
1073741822,server:127.0.0.2
start:
1610612733,server:127.0.0.3
start:
2147483644,server:127.0.0.4
+++++++++++++++++++++++++++++
127.0.0.2
127.0.0.3
127.0.0.4
127.0.0.1
127.0.0.3
127.0.0.4
127.0.0.5
127.0.0.2
127.0.0.2
127.0.0.3
+++++++++++++++++++++++++++++
start:
429496729,server:127.0.0.5
start:
536870911,server:127.0.0.1
start:
1073741822,server:127.0.0.2
start:
1610612733,server:127.0.0.3
start:
2147483644,server:127.0.0.4
+++++++++++++++++++++++++++++
127.0.0.2
127.0.0.3
127.0.0.5
127.0.0.1
127.0.0.3
127.0.0.5
127.0.0.5
127.0.0.2
127.0.0.2
127.0.0.3
+++++++++++++++++++++++++++++
start:
429496729,server:127.0.0.5
start:
536870911,server:127.0.0.1
start:
1073741822,server:127.0.0.2
start:
1610612733,server:127.0.0.3

负载结果还是不错的!如果dummyNodeNum仔细分析并设置,在缓存量大的情况下效果也会不错。

 

 

 

 

posted @ 2009-12-29 21:00 overred 阅读(1562) 评论(5) 编辑 收藏

 回复 引用 查看   
#1楼 2009-12-30 10:10 suyang      
沙发呵呵、`
 回复 引用 查看   
#2楼 2010-01-28 11:02 绿野人      
不错支持!~年底了估计都挺忙的,还是希望多看到点文档,建议开一个QQ群组,大家集中讨论集中学习!
 回复 引用 查看   
#3楼[楼主] 2010-01-28 13:04 overred      
@绿野人
有劳那位给建一个吧。Thx

 回复 引用 查看   
#4楼 2011-04-27 17:54 满天都是比卡丘      
学习ING!
 回复 引用 查看   
#5楼 2011-09-14 13:47 kkun      
还是不太理解!