前言:对于一致性哈希已经不是罕见概念,在此只是对原有理论概念的一个整理和用自己的理解讲述,希望对新手有些许帮助,利人利己足矣。
1.概念
一致哈希是一种特殊的哈希算法。在使用一致哈希算法后,哈希表槽位数(大小)的改变平均只需要对 K/n 个关键字重新映射,其中 K是关键字的数量,n是槽位数量。然而在传统的哈希表中,添加或删除一个槽位的几乎需要对所有关键字进行重新映射。
一致哈希将每个对象映射到圆环边上的一个点,系统再将可用的节点机器映射到圆环的不同位置。查找某个对象对应的机器时,需要用一致哈希算法计算得到对象对应圆环边上位置,沿着圆环边上查找直到遇到某个节点机器,这台机器即为对象应该保存的位置。 当删除一台节点机器时,这台机器上保存的所有对象都要移动到下一台机器。添加一台机器到圆环边上某个点时,这个点的下一台机器需要将这个节点前对应的对象移动到新机器上。 更改对象在节点机器上的分布可以通过调整节点机器的位置来实现。
2.应用场景
在使用N台缓存服务器时,一种常用的负载均衡方式是,对资源Object的请求使用 Hash(Object)%N 来映射到某一台缓存服务器。但是缺点有二:
1.一个服务器(服务器A)Down掉之后,服务器A中的缓存资源都会失效,映射公式变成了Hash(Object)%(N-1), 缓存失效,这会使得缓存服务器大量集中地向原始内容服务器更新缓存。
2.当增加或减少一台缓存服务器时,映射公式变成了Hash(Object)%(N+1),这种方式可能会改变所有资源对应的Hash值,也就是所有的缓存都失效了,这会使得缓存服务器大量集中地向原始内容服务器更新缓存。
因些,需要一致哈希算法来避免这样的问题。 一致哈希尽可能使同一个资源映射到同一台缓存服务器。这种方式要求增加一台缓存服务器时,新的服务器尽量分担存储其他所有服务器的缓存资源。减少一台缓存服务器时,其他所有服务器也可以尽量分担存储它的缓存资源。 一致哈希算法的主要思想是将每个缓存服务器与一个或多个哈希值域区间关联起来,其中区间边界通过计算缓存服务器对应的哈希值来决定。(定义区间的哈希函数不一定和计算缓存服务器哈希值的函数相同,但是两个函数的返回值的范围需要匹配。)如果一个缓存服务器被移除,则它会从对应的区间会被并入到邻近的区间,其他的缓存服务器不需要任何改变。
3.原理简析
3.1环形Hash空间
考虑通常的Hash算法都是将value 映射到一个32位的 key 值上,也就是 0~2^32-1次方的数值空间。我们可以将这个空间想象成一个首( 0 )尾( 2^32-1 )相接的圆环,如下图1所示:
图1 环形Hash空间
3.2把对象映射到环形Hash空间
下面考虑 4 个资源对象 object1,object2,object3,bject4 ,通过 Hash 函数计算出的 Hash 值(key)在环上的分布如图 2 所示:
图2 资源对象的Key值分布
如此:
Key1 = Hash(Object1);
······
Key4 = Hash(Object4);
3.3把资源服务器映射到环形Hash空间
一致性Hash的基本思想就是将资源对象和缓存服务器(cache)都映射到同一个Hash数值空间中,并且使用相同的Hash算法。缓存服务器(cache)的Hash计算,一般的方法可以使用服务器(cache)机器的IP地址或者机器名作为Hash输入。
假设当前有 A,B 和 C 共 3 台缓存服务器(cache) ,那么其映射结果将如图 3 所示,他们在 hash 空间中,以对应的 hash值排列。
图3 服务器和资源对象的Key值分布
如此:
KeyA = Hash(CacheA);
······
KeyC = Hash(CacheC);
3.4把资源对象映射到缓存服务器
现在服务器(cache)和资源对象都已经通过同一个Hash算法映射到Hash数值空间中了,接下来要考虑的就是如何将对象映射到服务器(cache)上面。
在这个环形空间中,如果沿着顺时针方向从对象的 key 值出发,直到遇见一个服务器(cache),那么就将该对象存储在这个缓存服务器上,因为对象和服务器的Hash 值是固定的,因此这个服务器必然是唯一和确定的。这就是对象和服务器的映射方法。
依然继续上面的例子(参见图 3 ),那么根据上面的方法,对象 object1 将被存储到 cache A 上; object2和 object3 对应到 cache C ; object4 对应到 cache B ;
3.5考虑服务器的增减
根据上面的映射方法(顺时针寻找)我们可以很容易的想象服务器的增减导致资源对象映射的改变情况。如图4和5所示:
图4 缓存服务器B被移除后的映射改变
图5 添加缓存服务器D后的映射改变
根据上文所述,一致性哈希中节点的改变需要K/N个关键字的映射,N是缓存服务器个数,K是资源对象的个数,显然4/3和4/4取整都为1,与以上结果相符。
3.6采用虚拟节点保证平衡性
在分布式缓存中,都希望资源对象能够尽可能的均匀分布到缓存服务器中,这也是平衡性的要求。但是,一致性哈希并不能保证绝对的平衡性。举个例子,在服务器(cache)较少情况下,资源对象并不能被均匀的映射到服务器上,比如在上面的例
子中,仅部署 cache A 和 cache C 的情况下,在 4 个资源对象中, cache A 仅存储了 object1 ,而 cache C 则存储了 object2 、 object3 和 object4 。显然,分布是很不平衡的。
那么,为解决这种情况,一致性哈希引进了虚拟节点的概念。
虚拟节点( virtual node )是实际服务器节点在Hash空间中的一个或多个复制品(replica),一实际个节点对应了若干个“虚拟节点”,这个对应个数也就是“复制个数”,“虚拟节点”在Hash空间中以Hash值排列。
仍以仅部署 cache A 和 cache C 的情况为例,在图 4 中我们已经看到,cache节点分布并不均匀。现在我们引入虚拟节点,并设置“复制个数”为 2 ,这就意味着一共会存在 4 个“虚拟节点”, cache A1, cache A2 代表了cache A; cache C1, cache C2 代表了 cache C;假设一种比较理想的情况,如图6:
图6 引入虚拟节点后的映射关系
此时,资源对象到“虚拟节点”的映射关系为:objec1->cache A2 ; objec2->cache A1 ; objec3->cache C1 ; objec4->cache C2 ;因此对象 object1 和 object2 都被映射到了 cache A 上,而 object3 和 object4 映射到了 cache C 上;平衡性有了很大提高。
引入“虚拟节点”后,映射关系就从 { 对象 -> 节点 } 转换到了 { 对象 -> 虚拟节点 } 。查询物体所在 cache时的映射关系如图 7 所示:
图7 查询资源对象的计算过程
虚拟节点的Hash值计算可以采用对应节点的 IP 地址加数字后缀的方式。例如假设 cache A 的 IP 地址为202.168.14.241 。
引入虚拟节点前,计算 cache A 的 Hash 值:Hash(“202.168.14.241”);
引入虚拟节点后,计算虚拟节点cache A1 和的Hash值:Hash(“202.168.14.241#1”);
下面这个URL中讲的很详细:http://www.codeproject.com/Articles/56138/Consistent-hashing
4.C++实现
一致性哈希还必须解决两个问题,一个是用于节点存储和查找的数据结构的选择,另一个是Hash算法的选择。
我们可以想象,节点是均匀的分布在你一个圆环上,也就是说节点Hash值可以存储在一个有序队列上。同时,该数据结构应该高效地支持节点频繁的增删,和理想的查询效率,那么自然会想到红黑树,它是一颗近似平衡的二叉树,因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。因此,用于节点存储和查找的数据结构我们选择红黑树,并实现其插入、删除、查找功能,并增加一个lookup函数,用于查找key中最小的节点。
Hash算法的选择,一致性哈希很重要的一点就是为了解决负载均衡问题,而虚拟节点是负载均衡的关键,所以,我们希望虚拟节点可以均匀地散步在圆环上。这里我们选择MD5算法,通过MD5算法,可以将一个标识串(用于标示虚拟结点)进行转化,得到一个16字节的字符数组,再对该数组进行处理,得到一个整型的Hash值。由于MD5具有高度的离散性,所以生成的Hash值也会具有很大的离散性,会均匀地散列到“环”上。
百闻不如一见,实际代码链接:
http://files.cnblogs.com/coser/ConsistentHashAlgorithm.rar
5.PHP实现
下面是转自http://blog.****.net/mayongzhan/article/details/4298834的PHP实现: