一致性哈希算法在1997年由麻省理工学院的Karger等人在解决分布式Cache中提出的,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分相似。一致性哈希修正了CARP使用的简单哈希算法带来的问题,使得DHT能够在P2P环境中真正获得应用。前端
如今一致性hash算法在分布式系统中也获得了普遍应用,分布式系统中涉及到集群部署,包括缓存Redis集群,数据库集群,咱们在使用Redis的时候,为了保证Redis的高可用,提升Redis的读写性能,最简单的方式咱们会作主从复制,组成Master-Master或者Master-Slave的形式,或者搭建Redis集群,进行数据的读写分离,相似于数据库的主从复制和读写分离。以下所示:java
一样数据库中也是,当单表数据大于500W的时候须要对其进行分库分表,当数据量很大的时候(标准可能不同,要看Redis服务器容量)咱们一样能够对Redis进行相似的操做,就是分库分表。算法
假设,咱们有一个社交网站,须要使用Redis存储图片资源,存储的格式为键值对,key值为图片名称,value为该图片所在文件服务器的路径,咱们须要根据文件名查找该文件所在文件服务器上的路径,数据量大概有2000W左右,按照咱们约定的规则进行分库,规则就是随机分配,咱们能够部署8台缓存服务器,每台服务器大概含有500W条数据,而且进行主从复制,示意图以下数据库
因为规则是随机的,全部咱们的一条数据都有可能存储在任何一组Redis中,例如上图咱们用户查找一张名称为”a.png”的图片,因为规则是随机的,咱们不肯定具体是在哪个Redis服务器上的,所以咱们须要进行一、二、三、4,4次查询才可以查询到(也就是遍历了全部的Redis服务器),这显然不是咱们想要的结果,有了解过的小伙伴可能会想到,随机的规则不行,可使用相似于数据库中的分库分表规则:按照Hash值、取模、按照类别、按照某一个字段值等等常见的规则就能够出来了!好,按照咱们的主题,咱们就使用Hash的方式。后端
可想而知,若是咱们使用Hash的方式 hash(图片名称) % N ,每一张图片在进行分库的时候均可以定位到特定的服务器,示意图以下:缓存
由于图片的名称是不重复的,因此,当咱们对同一个图片名称作相同的哈希计算时,得出的结果应该是不变的,若是咱们有4台服务器,使用哈希后的结果对4求余,那么余数必定是0、一、2或3,没错,正好与咱们以前的服务器编号相同。服务器
若是求余的结果为0, 咱们就把当前图片名称对应的图片缓存在0号服务器上;若是余数为1,就把当前图片名对应的图片缓存在1号服务器上;若是余数为2,同理。那么,当咱们访问任意一个图片的时候,只要再次对图片名称进行上述运算,便可得出对应的图片应该存放在哪一台缓存服务器上,咱们只要在这一台服务器上查找图片便可,若是图片在对应的服务器上不存在,则证实对应的图片没有被缓存,也不用再去遍历其余缓存服务器了,经过这样的方法,便可将3万张图片随机的分布到3台缓存服务器上了,并且下次访问某张图片时,直接可以判断出该图片应该存在于哪台缓存服务器上,这样就能知足咱们的需求了,咱们暂时称上述算法为HASH算法或者取模算法。分布式
上图中,假设咱们查找的是”a.png”,因为有4台服务器(排除从库),所以公式为函数
hash(a.png) % 4 = 2
,可知定位到了第2号服务器,这样的话就不会遍历全部的服务器,大大提高了性能!性能
上述的方式虽然提高了性能,咱们再也不须要对整个Redis服务器进行遍历!可是,使用上述HASH算法进行缓存时,会出现一些缺陷,主要体如今服务器数量变更的时候,全部缓存的位置都要发生改变!
试想一下,若是3台缓存服务器已经不能知足咱们的缓存需求,那么咱们应该怎么作呢?没错,很简单,多增长两台缓存服务器不就好了,假设,咱们增长了一台缓存服务器,那么缓存服务器的数量就由4台变成了5台,此时,若是仍然使用上述方法对同一张图片进行缓存,那么这张图片所在的服务器编号一定与原来4台服务器时所在的服务器编号不一样,由于除数由4变为了5,被除数不变的状况下,余数确定不一样,这种状况带来的结果就是当服务器数量变更时,全部缓存的位置都要发生改变,换句话说,当服务器数量发生改变时,全部缓存在必定时间内是失效的,当应用没法从缓存中获取数据时,则会向后端服务器请求数据,同理,假设4台缓存中忽然有一台缓存服务器出现了故障,没法进行缓存,那么咱们则须要将故障机器移除,可是若是移除了一台缓存服务器,那么缓存服务器数量从4台变为3台,若是想要访问一张图片,这张图片的缓存位置一定会发生改变,之前缓存的图片也会失去缓存的做用与意义,因为大量缓存在同一时间失效,形成了缓存的雪崩,此时前端缓存已经没法起到承担部分压力的做用,后端服务器将会承受巨大的压力,整个系统颇有可能被压垮,因此,咱们应该想办法不让这种状况发生,可是因为上述HASH算法自己的缘故,使用取模法进行缓存时,这种状况是没法避免的。
咱们来回顾一下使用上述算法会出现的问题。
问题1:当缓存服务器数量发生变化时,会引发缓存的雪崩,可能会引发总体系统压力过大而崩溃(大量缓存同一时间失效)。
问题2:当缓存服务器数量发生变化时,几乎全部缓存的位置都会发生改变,怎样才能尽可能减小受影响的缓存呢?
其实,上面两个问题是一个问题,那么,一致性哈希算法可以解决上述问题吗?解决这些问题,一致性哈希算法诞生了。
一致性Hash算法也是使用取模的方法,只是,刚才描述的取模法是对服务器的数量进行取模,而一致性Hash算法是对2^32取模,什么意思呢?简单来讲,一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0-2^32-1(即哈希值是一个32位无符号整形),整个哈希环以下:
整个空间按顺时针方向组织,圆环的正上方的点表明0,0点右侧的第一个点表明1,以此类推,二、三、四、五、6……直到2^32-1,也就是说0点左侧的第一个点表明2^32-1, 0和2^32-1在零点中方向重合,咱们把这个由2^32个点组成的圆环称为Hash环。
那么,一致性哈希算法与上图中的圆环有什么关系呢?咱们继续聊,仍然以以前描述的场景为例,假设咱们有4台缓存服务器,服务器A、服务器B、服务器C,服务器D,那么,在生产环境中,这4台服务器确定有本身的IP地址或主机名,咱们使用它们各自的IP地址或主机名做为关键字进行哈希计算,使用哈希后的结果对2^32取模,可使用以下公式示意:
hash(服务器A的IP地址) % 2^32
经过上述公式算出的结果必定是一个0到2^32-1之间的一个整数,咱们就用算出的这个整数,表明服务器A,既然这个整数确定处于0到2^32-1之间,那么,上图中的hash环上一定有一个点与这个整数对应,而咱们刚才已经说明,使用这个整数表明服务器A,那么,服务器A就能够映射到这个环上。
以此类推,下一步将各个服务器使用相似的Hash算式进行一个哈希,这样每台机器就能肯定其在哈希环上的位置,这里假设将上文中四台服务器使用IP地址哈希后在环空间的位置以下:
接下来使用以下算法定位数据访问到相应服务器: 将数据key使用相同的函数Hash计算出哈希值,并肯定此数据在环上的位置,今后位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器!
例如咱们有Object A、Object B、Object C、Object D四个数据对象,通过哈希计算后,在环空间上的位置以下:
根据一致性Hash算法,数据A会被定为到Node A上,B被定为到Node B上,C被定为到Node C上,D被定为到Node D上。
说到这里可能会有疑问,为何hash一致性的数据空间范围是2^32次方?
由于,java中int的最大值是2^31-1最小值是-2^31,2^32恰好是无符号整形的最大值;
进一步追尾基础,为何java中int的最大值是2^31-1最小值是-2^31?
由于,int的最大值最小值范围设定是由于一个int占4个字节,一个字节占8位,二进制中恰好是32位。(基础忘记的须要恶补一下了)
现假设Node C不幸宕机,能够看到此时对象A、B、D不会受到影响,只有C对象被重定位到Node D。通常的,在一致性Hash算法中,若是一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响,以下所示:
下面考虑另一种状况,若是在系统中增长一台服务器Node X,以下图所示:
此时对象Object A、B、D不受影响,只有对象C须要重定位到新的Node X !通常的,在一致性Hash算法中,若是增长一台服务器,则受影响的数据仅仅是新服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它数据也不会受到影响。
综上所述,一致性Hash算法对于节点的增减都只需重定位环空间中的一小部分数据,具备较好的容错性和可扩展性。
一致性Hash算法在服务节点太少时,容易由于节点分部不均匀而形成数据倾斜(被缓存的对象大部分集中缓存在某一台服务器上)问题,例如系统中只有两台服务器,其环分布以下:
此时必然形成大量数据集中到Node A上,而只有极少许会定位到Node B上,从而出现hash环偏斜的状况,当hash环偏斜之后,缓存每每会极度不均衡的分布在各服务器上,若是想要均衡的将缓存分布到2台服务器上,最好能让这2台服务器尽可能多的、均匀的出如今hash环上,可是,真实的服务器资源只有2台,咱们怎样凭空的让它们多起来呢,没错,就是凭空的让服务器节点多起来,既然没有多余的真正的物理服务器节点,咱们就只能将现有的物理节点经过虚拟的方法复制出来。
这些由实际节点虚拟复制而来的节点被称为"虚拟节点",即对每个服务节点计算多个哈希,每一个计算结果位置都放置一个此服务节点,称为虚拟节点。具体作法能够在服务器IP或主机名的后面增长编号来实现。
例如上面的状况,能够为每台服务器计算三个虚拟节点,因而能够分别计算 “Node A#1”、“Node A#2”、“Node A#3”、“Node B#1”、“Node B#2”、“Node B#3”的哈希值,因而造成六个虚拟节点:
同时数据定位算法不变,只是多了一步虚拟节点到实际节点的映射,例如定位到“Node A#1”、“Node A#2”、“Node A#3”三个虚拟节点的数据均定位到Node A上。这样就解决了服务节点少时数据倾斜的问题。在实际应用中,一般将虚拟节点数设置为32甚至更大,所以即便不多的服务节点也能作到相对均匀的数据分布。
上文中,咱们一步步分析了什么是一致性Hash算法,主要是考虑到分布式系统每一个节点都有可能失效,而且新的节点极可能动态的增长进来的状况,如何保证当系统的节点数目发生变化的时候,咱们的系统仍然可以对外提供良好的服务,这是值得考虑的!