大家好,我是小富~

集体公众号:程序员内点事,欢送学习交换

这两天看到技术群里,有小伙伴在探讨一致性hash算法的问题,正愁没啥写的题目就来了,那就简略介绍下它的原理。下边咱们以分布式缓存中经典场景举例,面试中也是常常提及的一些话题,看看什么是一致性hash算法以及它有那些过人之处。

构建场景

如果咱们有三台缓存服务器编号node0node1node2,当初有3000万个key,心愿能够将这些个key平均的缓存到三台机器上,你会想到什么计划呢?

咱们可能首先想到的计划,是取模算法hash(key)% N,对key进行hash运算后取模,N是机器的数量。key进行hash后的后果对3取模,失去的后果肯定是0、1或者2,正好对应服务器node0node1node2,存取数据间接找对应的服务器即可,简略粗犷,齐全能够解决上述的问题。

hash的问题

取模算法尽管应用简略,但对机器数量取模,在集群扩容和膨胀时却有肯定的局限性,因为在生产环境中依据业务量的大小,调整服务器数量是常有的事;而服务器数量N发生变化后hash(key)% N计算的后果也会随之变动。

比方:一个服务器节点挂了,计算公式从hash(key)% 3变成了hash(key)% 2,后果会发生变化,此时想要拜访一个key,这个key的缓存地位大概率会产生扭转,那么之前缓存key的数据也会失去作用与意义。

大量缓存在同一时间生效,造成缓存的雪崩,进而导致整个缓存零碎的不可用,这基本上是不能承受的,为了解决优化上述情况,一致性hash算法应运而生~

那么,一致性哈希算法又是如何解决上述问题的?

一致性hash

一致性hash算法实质上也是一种取模算法,不过,不同于上边按服务器数量取模,一致性hash是对固定值2^32取模。

IPv4的地址是4组8位2进制数组成,所以用2^32能够保障每个IP地址会有惟一的映射

hash环

咱们能够将这2^32个值形象成一个圆环⭕️(不得意圆的,本人想个形态,好了解就行),圆环的正上方的点代表0,顺时针排列,以此类推,1、2、3、4、5、6……直到2^32-1,而这个由2的32次方个点组成的圆环统称为hash环

那么这个hash环和一致性hash算法又有什么关系嘞?咱们还是以上边的场景为例,三台缓存服务器编号node0node1node2,3000万个key

服务器映射到hash环

这个时候计算公式就从hash(key)% N 变成了hash(服务器ip)% 2^32,应用服务器IP地址进行hash计算,用哈希后的后果对2^32取模,后果肯定是一个0到2^32-1之间的整数,而这个整数映射在hash环上的地位代表了一个服务器,顺次将node0node1node2三个缓存服务器映射到hash环上。

对象key映射到hash环

接着在将须要缓存的key对象也映射到hash环上,hash(key)% 2^32,服务器节点和要缓存的key对象都映射到了hash环,那对象key具体应该缓存到哪个服务器上呢?

对象key映射到服务器

从缓存对象key的地位开始,沿顺时针方向遇到的第一个服务器,便是以后对象将要缓存到的服务器

因为被缓存对象与服务器hash后的值是固定的,所以,在服务器不变的条件下,对象key必定会被缓存到固定的服务器上。依据上边的规定,下图中的映射关系:

  • key-1 -> node-1
  • key-3 -> node-2
  • key-4 -> node-2
  • key-5 -> node-2
  • key-2 -> node-0

如果想要拜访某个key,只有应用雷同的计算形式,即可得悉这个key被缓存在哪个服务器上了。

一致性hash的劣势

咱们简略理解了一致性hash的原理,那它又是如何优化集群中增加节点和缩减节点,一般取模算法导致的缓存服务,大面积不可用的问题呢?

先来看看扩容的场景,如果业务量激增,零碎须要进行扩容减少一台服务器node-4,刚好node-4被映射到node-1node-2之间,沿顺时针方向对象映射节点,发现本来缓存在node-2上的对象key-4key-5被从新映射到了node-4上,而整个扩容过程中受影响的只有node-4node-1节点之间的一小部分数据。

反之,如果node-1节点宕机,沿顺时针方向对象映射节点,缓存在node-1上的对象key-1被从新映射到了node-4上,此时受影响的数据只有node-0node-1之间的一小部分数据。

从上边的两种状况发现,当集群中服务器的数量产生扭转时,一致性hash算只会影响少部分的数据,保障了缓存零碎整体还能够对外提供服务的。

数据偏斜问题

前边为了便于了解原理,画图中的node节点都很理想化的绝对均匀分布,但现实和理论的场景往往差异很大,就比方办了个健身年卡的我,只去过健身房两次,还只是洗了个澡。

在服务器节点数量太少的状况下,很容易因为节点散布不平均而造成数据歪斜问题,如下图被缓存的对象大部分缓存在node-4服务器上,导致其余节点资源节约,零碎压力大部分集中在node-4节点上,这样的集群是十分不衰弱的。

解决数据歪斜的方法也简略,咱们就要想方法让节点映射到hash环上时,绝对散布平均一点。

一致性Hash算法引入了一个虚构节点机制,即对每个服务器节点计算出多个hash值,它们都会映射到hash环上,映射到这些虚构节点的对象key,最终会缓存在实在的节点上。

虚构节点的hash计算通常能够采纳,对应节点的IP地址加数字编号后缀 hash(10.24.23.227#1) 的形式,举个例子,node-1节点IP为10.24.23.227,失常计算node-1的hash值。

  • hash(10.24.23.227#1)% 2^32

假如咱们给node-1设置三个虚构节点,node-1#1node-1#2node-1#3,对它们进行hash后取模。

  • hash(10.24.23.227#1)% 2^32
  • hash(10.24.23.227#2)% 2^32
  • hash(10.24.23.227#3)% 2^32

下图退出虚构节点后,原有节点在hash环上散布的就绝对平均了,其余节点压力失去了摊派。

但须要留神一点,调配的虚构节点个数越多,映射在hash环上才会越趋于平均,节点太少的话很难看出成果

引入虚构节点的同时也减少了新的问题,要做虚构节点和实在节点间的映射,对象key->虚构节点->理论节点之间的转换。

一致性hash的利用场景

一致性hash在分布式系统中应该是实现负载平衡的首选算法,它的实现比拟灵便,既能够在客户端实现,也能够在中间件上实现,比方日常应用较多的缓存中间件memcachedredis集群都有用到它。

memcached的集群比拟非凡,严格来说它只能算是伪集群,因为它的服务器之间不能通信,申请的散发路由齐全靠客户端来的计算出缓存对象应该落在哪个服务器上,而它的路由算法用的就是一致性hash。

还有redis集群中hash槽的概念,尽管实现不尽相同,但思维万变不离其宗,看完本篇的一致性hash,你再去了解redis槽位就轻松多了。

其它的利用场景还有很多:

  • RPC框架Dubbo用来抉择服务提供者
  • 分布式关系数据库分库分表:数据与节点的映射关系
  • LVS负载平衡调度器
  • .....................

总结

简略的论述了下一致性hash,如果有不对的中央大家能够留言斧正,任何技术都不会美中不足,一致性Hash算法也是有一些潜在隐患的,如果Hash环上的节点数量十分宏大或者更新频繁时,检索性能会比拟低下,而且整个分布式缓存须要一个路由服务来做负载平衡,一旦路由服务挂了,整个缓存也就不可用了,还要思考做高可用。

不过话说回来,只有是能解决问题的都是好技术,有点副作用还是能够忍耐的。