一、前言
在解决分布式系统中负载均衡的问题时候可以使用Hash算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载均衡的作用。
但是普通的余数hash(hash(比如用户id)%服务器机器数)算法伸缩性很差,当新增或者下线服务器机器时候,用户id与服务器的映射关系会大量失效。一致性hash则利用hash环对其进行了改进。
二、一致性Hash概述
为了能直观的理解一致性hash原理,这里结合一个简单的例子来讲解,假设有4台服务器,地址为ip1,ip2,ip3,ip4。
-
一致性hash是首先计算四个ip地址对应的hash值
hash(ip1),hash(ip2),hash(ip3),hash(ip3),计算出来的hash值是0~最大正整数直接的一个值,这四个值在一致性hash环上呈现如下图: -
hash环上顺时针从整数0开始,一直到最大正整数,我们根据四个ip计算的hash值肯定会落到这个hash环上的某一个点,至此我们把服务器的四个ip映射到了一致性hash环
-
当用户在客户端进行请求时候,首先根据hash(用户id)计算路由规则(hash值),然后看hash值落到了hash环的那个地方,根据hash值在hash环上的位置顺时针找距离最近的ip作为路由ip.
如上图可知user1,user2的请求会落到服务器ip2进行处理,User3的请求会落到服务器ip3进行处理,user4的请求会落到服务器ip4进行处理,user5,user6的请求会落到服务器ip1进行处理。
下面考虑当ip2的服务器挂了的时候会出现什么情况?
当ip2的服务器挂了的时候,一致性hash环大致如下图:
根据顺时针规则可知user1,user2的请求会被服务器ip3进行处理,而其它用户的请求对应的处理服务器不变,也就是只有之前被ip2处理的一部分用户的映射关系被破坏了,并且其负责处理的请求被顺时针下一个节点委托处理。
下面考虑当新增机器的时候会出现什么情况?
当新增一个ip5的服务器后,一致性hash环大致如下图:
根据顺时针规则可知之前user1的请求应该被ip1服务器处理,现在被新增的ip5服务器处理,其他用户的请求处理服务器不变,也就是新增的服务器顺时针最近的服务器的一部分请求会被新增的服务器所替代。
三、一致性hash的特性
-
单调性(Monotonicity),单调性是指如果已经有一些请求通过哈希分派到了相应的服务器进行处理,又有新的服务器加入到系统中时候,应保证原有的请求可以被映射到原有的或者新的服务器中去,而不会被映射到原来的其它服务器上去。 这个通过上面新增服务器ip5可以证明,新增ip5后,原来被ip1处理的user6现在还是被ip1处理,原来被ip1处理的user5现在被新增的ip5处理。
-
分散性(Spread):分布式环境中,客户端请求时候可能不知道所有服务器的存在,可能只知道其中一部分服务器,在客户端看来他看到的部分服务器会形成一个完整的hash环。如果多个客户端都把部分服务器作为一个完整hash环,那么可能会导致,同一个用户的请求被路由到不同的服务器进行处理。这种情况显然是应该避免的,因为它不能保证同一个用户的请求落到同一个服务器。所谓分散性是指上述情况发生的严重程度。好的哈希算法应尽量避免尽量降低分散性。 一致性hash具有很低的分散性
-
平衡性(Balance):平衡性也就是说负载均衡,是指客户端hash后的请求应该能够分散到不同的服务器上去。一致性hash可以做到每个服务器都进行处理请求,但是不能保证每个服务器处理的请求的数量大致相同,如下图
服务器ip1,ip2,ip3经过hash后落到了一致性hash环上,从图中hash值分布可知ip1会负责处理大概80%的请求,而ip2和ip3则只会负责处理大概20%的请求,虽然三个机器都在处理请求,但是明显每个机器的负载不均衡,这样称为一致性hash的倾斜,虚拟节点的出现就是为了解决这个问题。
五、虚拟节点
当服务器节点比较少的时候会出现上节所说的一致性hash倾斜的问题,一个解决方法是多加机器,但是加机器是有成本的,那么就加虚拟节点。
为了解决这个问题,一致哈希算法引入了虚拟节点。将每一个节点都进行多次 hash,生成多个节点放置在环上称为虚拟节点。
比如上面三个机器,每个机器引入1个虚拟节点后的一致性hash环的图如下:
其中ip1-1是ip1的虚拟节点,ip2-1是ip2的虚拟节点,ip3-1是ip3的虚拟节点。
可知当物理机器数目为M,虚拟节点为N的时候,实际hash环上节点个数为M*N。比如当客户端计算的hash值处于ip2和ip3或者处于ip2-1和ip3-1之间时候使用ip3服务器进行处理。
六、均匀一致性hash
上节我们使用虚拟节点后的图看起来比较均衡,但是如果生成虚拟节点的算法不够好很可能会得到下面的环:
可知每个服务节点引入1个虚拟节点后,情况相比没有引入前均衡性有所改善,但是并不均衡。
均衡的一致性hash应该是如下图:
均匀一致性hash的目标是如果服务器有N台,客户端的hash值有M个,那么每个服务器应该处理大概M/N个用户的。也就是每台服务器负载尽量均衡
七 问题
但是一致性哈希算法的提出当然也同样伴随着新的问题出现,那就是当某一个服务器结点挂掉之后,它的任务就会分配到它的下一个服务器结点,那么这就有悖于分布式系统中需要满足平衡性的要求。
雪崩效应
在服务器上会有一些数据会经常被访问,这些数据的访问次数远远高于其他数据,那么这些数据就被称为热点数据,理所当然在分布式服务器中承载这些热点数据的服务器的负载就要大于其他服务器。当对热点数据的访问量超过服务器的承受限度的时候,服务器就会挂掉。
按照一致性哈希算法,这个服务器的数据就会托管给下一个服务器,下一个服务器当然也无法承担这么大的请求量,不一会它也会挂掉,接着下一个服务器也会挂掉,直到最后整个服务器挂掉。这就是雪崩。
优化方案
这里有两种优化方案:
- 第一种简单粗暴,那就是增加承载热点数据的服务器数量。
- 另一种更好的方案是使用虚拟结点技术,这种方法的原理就是将一个物理结点拆分问多个虚拟结点,让这些虚拟结点均匀的分布在哈希环之上。这样就解决了某个结点删除后,它的数据资源分配不平衡的问题。
如图红色的结点3就相当于承载热点数据的服务器,右图中把每个物理结点拆分为了两个虚拟结点,并均匀的分布在哈希环之上。
这种解决方案的优势是与此同时也解决了当增加结点时,新结点从其他节点拉取资源后导致的结点资源分布不均问题。
例如,有ABD三个结点分配一百个资源,当要在BD之间加入一个结点C的时候,C结点会直接从D结点拉取相应的结点,这就会导致AB分配的资源肯定要多于CD所分配的资源,这也就不满足服务器的负载均衡的要求。而当插入一个物理结点时把它拆分为几个虚拟结点并均匀分布在哈希环之上就可以解决这个问题。
时间复杂度
做到这一步好像一致性哈希已经很完美了,但是我们还忽略了一个问题,那就是一致性哈希的查找时间复杂度。一致性哈希不像普通哈希,时间复杂度是0(1),这是因为普通的哈希是基于数组的,而一致性哈希为了满足可伸缩性一般会选择链表作为基础数据结构。那么时间复杂度就会变为O(N)。
优化方案
这里O(N)的时间复杂度对于哈希算法是不能忍受的,在这里我们使用一种叫做跳转表的技术来解决这个问题。
如图
如上图在这个跳转表中,每个结点记录距离自己 1,2,4 距离的数字所存的结点,这样不管查询落在哪个节点上,对整个哈希环上任意的查询一次都可以至少跳过一半的查询空间,这样递归下去很快就可以定位到数据是存在哪个结点上。这样时间复杂度就降为了O(logN)。但是这样也同样会带来一个问题就是会占用服务器的存储空间。
第二种解决方案就是不选用链表作为基础的数据结构,换成二叉查找树结构。因为哈希查找的过程实际上就是在二叉树中查找不小于查找数的最小数值的过程,所以我们可以按照需求选取AVL树,红黑树作基础数据结构。这样也可以降低时间复杂度到O(logN)。
八、总结
在分布式系统中一致性hash起着不可忽略的地位,无论是分布式缓存,还是分布式Rpc框架的负载均衡策略都有所使用。
参考
http://www.spring4all.com/article/988?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io