首页 > 代码库 > 一致性哈希算法
一致性哈希算法
时间:2014.07.17
地点:基地二楼
----------------------------------------------------------------------------------------
一、为什么我们需要一致性哈希算法
考虑一个场景:服务器负载均衡问题。有n台服务器,比如n台cache,cache编号的选择和object的匹配应该采取什么用的策略才能满足保证功能的需求。如果我们采取如下简单的哈希映射关系:
hash(object) mod n
看起来这样一个系统工作正常,但考虑周到会有如下几个问题:
问题1:有一天我们需要增加一台服务器,这时我们需要改变哈希映射关系为:
hash(object) mod (n+1)
问题2:有一天我们需要删除一台服务器,这时我们需要改变哈希映射关系为:
hash(object) mod (n-1)
问题3:怎么样使得服务器负载分配均匀
现在我们看到麻烦来了,由于hash关系的变化,几乎所有的object将会被hash到新的位置,即映射到新的服务器,这是一种灾难,于是我们就需要一致性哈希来改善这种情况了。
----------------------------------------------------------------------------------------
二、一致性哈希
一致性哈希可以保证当任意一台服务器增加到系统或从系统中删除时,仅仅只有相关的有限个object需要重新匹配,即一致性哈希最大程度地防止object与服务器之前的匹配关系。----------------------------------------------------------------------------------------
三、哈希空间(hash space)
一般地,哈希函数将object映射到一个位的值上,哈希值取值范围为[0~2^32-1],如下图所示,我们把哈希值域首尾相接联合成一个环形,于是也称呼为环形哈希空间。
哈希空间
----------------------------------------------------------------------------------------
四、将object映射到哈希空间
假设有4个object,分别为object1~object4,现在我们使用哈希函数获得他们各自的key值,并映射到环形哈希空间中去,如下图所示:hash(object1)=key1;
hash(object2)=key2;
hash(object3)=key3;
hash(object4)=key4;
将object映射到哈希空间
----------------------------------------------------------------------------------------
五、将cache映射到哈希空间
我们采用同样的哈希函数,继续还将服务器也映射到该环形哈希空间中,假设我们有3台服务器A,B,C,哈希之后如下:
hash(cacheA)=keyA;
hash(cacheB)=keyB;
hash(cacheC)=keyC;
将cache以同样的方式也映射到哈希空间
----------------------------------------------------------------------------------------
六、将object与cache匹配
经过之前的步骤,现在object和cache都已经成功映射到环形哈希空间去了。接下来,我们将决定objects怎么和cache形成映射:我们采取的策略是,将object按顺时针方向走,直到找到第一个cache,若果该cache是可用的,即形成object和cache匹配,否则继续寻找下一个cache。根据上述原则,在这里我们得到的匹配结果是:object1——>cacheA
object2——>cacheC
object3——>cacheC
object4——>cacheB
----------------------------------------------------------------------------------------
七、增加或删减cache
现在考虑两种场景:1.当某一cache崩溃移除系统 ,由于object4是映射在cacheB上的,现在cacheB将被移除了,那么现在object4得重现更新这个映射,我们只需要简单的沿顺时针方向找到下一个可用的cache,在这里是cacheC即可,如图,而不必所以映射关系全盘改动。
cacheB崩溃
当将cacheD加入在object2和object3之间时,B和D之间的object也需重新映射,在这里object2将绑定到新加入的cacheD上。如下图:
增加cacheD
----------------------------------------------------------------------------------------
八、虚节点
上述情况能够很好的解决增加删除服务器节点时对整个系统大动干戈的问题。但还存在一个问题,那就是如果环形哈希空间上的cache较少的话,object的部署不会那么均匀。于是我们引入虚节点的概念。 ,它能够比较好的改善这一缺点。 虚节点是环形哈希空间上哥cache点的副本,每个cache关联着几个环形上的虚节点,当我们增加一个节点时,这意味着我们实际上在环形空间上增加了几个这样的虚节点,同样删除某一cache时,我们也将移除环形空间上所有和它相关的虚节点。继续考虑上面的例子,现在系统中有cachA和cacheC,引入虚节点,并假设各存在2份,于是在环形空间上,一起有4个虚节点。cacheA1和cacheA2代表cacheA,还有cacheC1和cacheC2代表C,如下图:于是,现在从object到虚节点的映射为:
object1——>cacheA2;
object2——>cacheA1;
object3——>cacheC1;
object4——>cacheC2
这样分配就会显得相对均匀。如下图:
九、一致性哈希算法的应用
最后来个实际应用的例子。问题描述: 例如手机朋友网有n个服务器,为了方便用户的访问会在服务器上缓存数据,因此用户每次访问的时候最好能保持同一台服务器。
已有的做法是根据ServerIPIndex[QQNUM%n]得到请求的服务器,这种方法很方便将用户分到不同的服务器上去。但是如果一台服务器死掉了,那么n就变为了n-1,那么ServerIPIndex[QQNUM%n]与ServerIPIndex[QQNUM%(n-1)]基本上都不一样了,所以大多数用户的请求都会转到其他服务器,这样会发生大量访问错误。
问: 如何改进或者换一种方法,使得:
(1)一台服务器死掉后,不会造成大面积的访问错误,
(2)原有的访问基本还是停留在同一台服务器上;
(3)尽量考虑负载均衡。
显然,传统的办法题目已经给出,即用模余方法:做法很简单,但存在很多问题不满足需求。于是我们考虑一致性哈希算法。正如前面所述。
其它应用场景:
在做服务器负载均衡时候可供选择的负载均衡的算法有很多,包括: 轮循算法(Round Robin)、哈希算法(HASH)、最少连接算法(Least Connection)、响应速度算法(Response Time)、加权法(Weighted )等。其中哈希算法是最为常用的算法.
最典型的应用场景就是: 有N台服务器提供缓存服务,需要对服务器进行负载均衡,将请求平均分发到每台服务器上,每台机器负责1/N的服务。
常用的算法是对hash结果取余数 (hash() mod N):对机器编号从0到N-1,按照自定义的hash()算法,对每个请求的hash()值按N取模,得到余数i,然后将请求分发到编号为i的机器。但这样的算法方法存在致命问题,如果某一台机器宕机,那么应该落在该机器的请求就无法得到正确的处理,这时需要将当掉的服务器从算法从去除,此时候会有(N-1)/N的服务器的缓存数据需要重新进行计算;如果新增一台机器,会有N /(N+1)的服务器的缓存数据需要进行重新计算。对于系统而言,这通常是不可接受的颠簸(因为这意味着大量缓存的失效或者数据需要转移)。那么,如何设计一个负载均衡策略,使得受到影响的请求尽可能的少呢?
在Memcached、Key-Value Store、Bittorrent DHT、LVS中都采用了Consistent Hashing算法,可以说Consistent Hashing 是分布式系统负载均衡的首选算法。