(转) 一致性Hash算法在Memcached中的应用

前言

  大家应该都知道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的负载自然要均衡一些.就像地铁站出口一样,出口越多,每个出口出现拥挤的情况就会越少.

  代码实现:

 1 //保存所有虚拟节点信息, key : 虚拟节点的hash key, value: 虚拟节点对应的真实server
 2         private Dictionary<uint, string> hostDictionary = new Dictionary<uint, string>();
 3         //保存所有虚拟节点的hash key, 已按升序排序
 4         private uint[] ketamaHashKeys = new uint[] { };
 5         //保存真实server主机地址
 6         private string[] realHostArr = new string[] { };
 7         //每台真实server对应虚拟节点个数
 8         private int VirtualNodeNum = 250;
 9
10         public KetamaVirtualNodeInit(string[] hostArr)
11         {
12             this.realHostArr = hostArr;
13             this.InitVirtualNodes();
14         }
15
16         /// <summary>
17         /// 初始化虚拟节点
18         /// </summary>
19         private void InitVirtualNodes()
20         {
21             hostDictionary = new Dictionary<uint, string>();
22             List<uint> hostKeys = new List<uint>();
23             if (realHostArr == null || realHostArr.Length == 0)
24             {
25                 throw new Exception("不能传入空的Server集合");
26             }
27
28             for (int i = 0; i < realHostArr.Length; i++)
29             {
30                 for (int j = 0; j < VirtualNodeNum; j++)
31                 {
32                     byte[] nameBytes = Encoding.UTF8.GetBytes(string.Format("{0}-node{1}", realHostArr[i], j));
33                     //调用Ketama hash算法获取hash key
34                     uint hashKey = BitConverter.ToUInt32(new KetamaHash().ComputeHash(nameBytes), 0);
35                     hostKeys.Add(hashKey);
36                     if (hostDictionary.ContainsKey(hashKey))
37                     {
38                         throw new Exception("创建虚拟节点时发现相同hash key,请检查是否传入了相同Server");
39                     }
40                     hostDictionary.Add(hashKey, realHostArr[i]);
41                 }
42             }
43
44             hostKeys.Sort();
45             ketamaHashKeys = hostKeys.ToArray();
46         }

一致性hash算法的分配规则

  到此我们已经知道了所有虚拟节点的Hash值, 现在让我们来看下当我们拿到一个对象时如何存入Server, 或是拿到一个对象的Key时该如何取出对象.

Set一个对象时,先将对象的Key作为”Ketama”算法的Key,计算出Hash值后我们需要做下面几个步骤.

1:首先检查虚拟节点当中是否有与当前对象Hash值相等的,如有则直接将对象存入那个Hash值相等的节点,后面的步骤就不继续了.

2:如没有,则找出第一个比当前对象Hash值要大的节点,(节点的Hash值按升序进行排序,圆环上对应按照顺时针来排列),即离对象最近的节点,然后将对象存入该节点.

3:如果没有找到Hash值比对象要大的Server,证明对象的Hash值是介于最后一个节点和第一个节点之间的,也就是圆环上的E和A之间.这种情况就直接将对象存入第一个节点,即A.

  代码实现:  

 1 /// <summary>
 2         /// 根据hash key 获取对应的真实Server
 3         /// </summary>
 4         /// <param name="hash"></param>
 5         /// <returns></returns>
 6         public string GetHostByHashKey(string key)
 7         {
 8             byte[] bytes = Encoding.UTF8.GetBytes(key);
 9             uint hash = BitConverter.ToUInt32(new KetamaHash().ComputeHash(bytes), 0);
10
11             //寻找与当前hash值相等的Server.
12             int i = Array.BinarySearch(ketamaHashKeys, hash);
13
14             //如果i小于零则表示没有hash值相等的虚拟节点
15             if (i < 0)
16             {
17                 //将i继续按位求补,得到数组中第一个大于当前hash值的虚拟节点
18                 i = ~i;
19
20                 //如果按位求补后的i大于等于数组的大小,则表示数组中没有大于当前hash值的虚拟节点
21                 //此时直接取第一个server
22                 if (i >= ketamaHashKeys.Length)
23                 {
24                     i = 0;
25                 }
26             }
27
28             //根据虚拟节点的hash key 返回对应的真实server host地址
29             return hostDictionary[ketamaHashKeys[i]];
30         }

Get一个对象,同样也是通过”Ketama”算法计算出Hash值,然后与Set过程一样寻找节点,找到之后直接取出对象即可.

那么这个”Ketama”到底长什么样呢,让我们来看看代码实现.

 1 /// <summary>
 2     ///  Ketama hash加密算法
 3     ///  关于HashAlgorithm参见MSDN链接
 4     ///  http://msdn.microsoft.com/zh-cn/library/system.security.cryptography.hashalgorithm%28v=vs.110%29.aspx
 5     /// </summary>
 6     public class KetamaHash : HashAlgorithm
 7     {
 8
 9         private static readonly uint FNV_prime = 16777619;
10         private static readonly uint offset_basis = 2166136261;
11
12         protected uint hash;
13
14         public KetamaHash()
15         {
16             HashSizeValue = 32;
17         }
18
19         public override void Initialize()
20         {
21             hash = offset_basis;
22         }
23
24         protected override void HashCore(byte[] array, int ibStart, int cbSize)
25         {
26             int length = ibStart + cbSize;
27             for (int i = ibStart; i < length; i++)
28             {
29                 hash = (hash * FNV_prime) ^ array[i];
30             }
31         }
32
33         protected override byte[] HashFinal()
34         {
35             hash += hash << 13;
36             hash ^= hash >> 7;
37             hash += hash << 3;
38             hash ^= hash >> 17;
39             hash += hash << 5;
40             return BitConverter.GetBytes(hash);
41         }
42     }

测试性能

最后我把自己参考BeitMemcached写的算法与老代(Discuz!代震军)参考SPYMemcached写的做了一下对比.

源码在后面有下载.

结果:查找5W个key的时间比老代的版本快了100多倍,但在负载均衡方面差了一些.

测试数据:

   1:真实Server都是5台

2:随机生成5W个字符串key(生成方法直接拿老代的)

3:虚拟节点都是250个

我的版本:

老代的版本:

参考资料

BeitMemcached源码

老代: 一致性Hash算法(KetamaHash)的c#实现

总结一致性哈希(Consistent Hashing)

时间: 2024-10-13 11:58:22

(转) 一致性Hash算法在Memcached中的应用的相关文章

一致性hash算法在memcached中的使用

一.概述 1.我们的memcacheclient(这里我看的spymemcache的源代码).使用了一致性hash算法ketama进行数据存储节点的选择.与常规的hash算法思路不同.仅仅是对我们要存储数据的key进行hash计算,分配到不同节点存储.一致性hash算法是对我们要存储数据的server进行hash计算,进而确认每一个key的存储位置.  2.常规hash算法的应用以及其弊端 最常规的方式莫过于hash取模的方式.比方集群中可用机器适量为N,那么key值为K的的数据请求非常easy

一致性hash算法在内存数据库中的应用

由于redis是单点,但是项目中不可避免的会使用多台Redis缓存服务器,那么怎么把缓存的Key均匀的映射到多台Redis服务器上,且随着缓存服务器的增加或减少时做到最小化的减少缓存Key的命中率呢?这样就需要我们自己实现分布式. Memcached对大家应该不陌生,通过把Key映射到Memcached Server上,实现快速读取.我们可以动态对其节点增加,并未影响之前已经映射到内存的Key与memcached Server之间的关系,这就是因为使用了一致性哈希.因为Memcached的哈希策

一致性Hash算法及使用场景

一.问题产生背景      在使用分布式对数据进行存储时,经常会碰到需要新增节点来满足业务快速增长的需求.然而在新增节点时,如果处理不善会导致所有的数据重新分片,这对于某些系统来说可能是灾难性的. 那么是否有可行的方法,在数据重分片时,只需要迁移与之关联的节点而不需要迁移整个数据呢?当然有,在这种情况下我们可以使用一致性Hash来处理. 二.一致性Hash算法背景 一致性哈希算法在1997年由麻省理工学院的Karger等人在解决分布式Cache中提出的,设计目标是为了解决因特网中的热点(Hot

[转载] 一致性hash算法释义

转载自http://www.cnblogs.com/haippy/archive/2011/12/10/2282943.html 一致性Hash算法背景 一致性哈希算法在1997年由麻省理工学院的Karger等人在解决分布式Cache中提出的,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似.一致性哈希修正了CARP使用的简单哈希算法带来的问题,使得DHT可以在P2P环境中真正得到应用. 但现在一致性hash算法在分布式系统中也得到了广泛应用,研究过memcach

一致性Hash算法背景

一致性Hash算法背景 一致性哈希算法在1997年由麻省理工学院的Karger等人在解决分布式Cache中提出的,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似.一致性哈希修正了CARP使用的简单哈希算法带来的问题,使得DHT可以在P2P环境中真正得到应用. 但现在一致性hash算法在分布式系统中也得到了广泛应用,研究过memcached缓存数据库的人都知道,memcached服务器端本身不提供分布式cache的一致性,而是由客户端来提供,具体在计算一致性has

OpenStack_Swift源代码分析——Ring基本原理及一致性Hash算法

1.Ring的基本概念 Ring是swfit中最重要的组件.用于记录存储对象与物理位置之间的映射关系,当用户须要对Account.Container.Object操作时,就须要查询相应的Ring文件(Account.Container.Object都有自己相应的Ring),Ring 使用Region(近期几个版本号中新增加的).Zone.Device.Partition和Replica来维护这些信息,对于每个对象,依据你在部署swift设置的Replica数量,集群中会存有Replica个对象.

OpenStack_Swift源码分析——Ring基本原理及一致性Hash算法

1.Ring的基本概念 Ring是swfit中最重要的组件,用于记录存储对象与物理位置之间的映射关系,当用户需要对Account.Container.Object操作时,就需要查询对应的Ring文件(Account.Container.Object都有自己对应的Ring),Ring 使用Region(最近几个版本中新加入的).Zone.Device.Partition和Replica来维护这些信息,对于每一个对象,根据你在部署swift设置的Replica数量,集群中会存有Replica个对象.

一致性hash算法及java实现

一致性hash算法是分布式中一个常用且好用的分片算法.或者数据库分库分表算法.现在的互联网服务架构中,为避免单点故障.提升处理效率.横向扩展等原因,分布式系统已经成为了居家旅行必备的部署模式,所以也产出了几种数据分片的方法: 1.取模,2.划段,3.一致性hash 前两种有很大的一个问题就是需要固定的节点数,即节点数不能变,不能某一个节点挂了或者实时增加一个节点,变了分片规则就需要改变,需要迁移的数据也多. 那么一致性hash是怎么解决这个问题的呢? 一致性hash:对节点和数据,都做一次has

分布式memcached学习(四)&mdash;&mdash; 一致性hash算法原理

    分布式一致性hash算法简介 当你看到"分布式一致性hash算法"这个词时,第一时间可能会问,什么是分布式,什么是一致性,hash又是什么.在分析分布式一致性hash算法原理之前,我们先来了解一下这几个概念. 分布式 分布式(distributed)是指在多台不同的服务器中部署不同的服务模块,通过远程调用协同工作,对外提供服务. 以一个航班订票系统为例,这个航班订票系统有航班预定.网上值机.旅客信息管理.订单管理.运价计算等服务模块.现在要以集中式(集群,cluster)和分布