先来探讨一个问题:为什么要有hash一致性算法?
先抛出一个场景:假如我们现在使用的是redis集群,这个时候我们有三个节点,NodeA,NodeB,NodeC,假如我们没有按任何的规则存储数据在这三个节点中,那么当我们需要查找具体的一个key时,则需要将这三个节点全部都去查询一遍,这样效率就会很低了,很明显的不行。这个时候有的同学就会参照分库分表时可以搞个路由键,例如我们将hash(key)%3这样来将具体的Key路由到某一个节点,这样当我们下次需要去查某一个Key时,可以同样的按照hash(key)%3来直接去对应的节点去取出数据,貌似到现在为止,似乎解决了我们前面儿说的需要遍历所有节点的问题,但是一个问题解决了,另一个问题又来了,例如当我们的应用数据越来越多时,我们可能会加一台机器,这个时候按照路由规则hash(key)%4,那么之前存储的key就找不到了,或者当某一台机器宕机了,我们需要从集群中剔除掉这台机器,也会导致路由变成hash(key)%2,同样也会导致找不到之前的key了,这两种情况其实是一个问题,就是当我们集群中的节点发生变化时,都会导致之前存储的Key找不到了,那怎么解决呢?这个时候一致性hash就闪亮登场了。
一致性hash原理:
上面我们说将一个key经过hash算法后会取余节点数,而hash一致性算法中,是取余2的32次方,然后组成一个环,称为hash环。例如下图所示:
然后将我们的节点也按照取余2的32次方,让它们均匀的分布在hash环上.可以按照节点的ip进行hash或者其他的方式,例如我们有三个节点,结果可能是下面这样.
然后如果我们要存储具体的key或者取对应的Key时,同样要将hash(key)%2^32,让key分布在hash环上.如下图所示:
这个时候具体的key到底选取哪一个节点来存储呢?按照hash一致性算法,存储key的将是按照顺时针行走,离key最近的节点负责存储具体的key,例如上图中,keyA将存储在NodeA上,keyB将会存储在NodeB上,keyC将会存储在NodeC上。
好了,到现在为止,我们还只是介绍了hash一致性算法的大致原理,还没有讲解出它到底有什么好处,回到前面抛出的问题,当我们增加或减少节点的时候,按照hash一致性算法,到底会怎么样呢?例如现在我们的NodeB节点宕机了,如下图所示
这个时候keyA和keyC都不会受影响,它们都可以路由到对应的节点,但是keyB这个时候会路由到NodeC,所以受影响的知识NodeA到NodeB之间的一部分,这一部分需要重新路由刷新。可以看到相较于之前的所有的缓存都不可用,可能导致缓存击穿问题,而是用hash一致性算法,只会有部分区域受影响。所以hash一致性算法并没有完全的一致性算法,它只是扩大取余的基数,然后使受影响的数据范围减少而已。
hash一致性算法还可能会有分布不均匀的情况,如下图所示:
这种情况下,大部分的key都会跑到NodeA上,就会导致数据倾斜的问题了。这个时候可以采用虚拟节点的方式来解决这个问题。例如将NodeA虚拟出三个节点NodeA#1,NodeA#2,NodeA#3,NodeB#1,NodeB#2,NodeB#3,然后将虚拟节点让它们分布在hash环上。如下图所示
例如按照上述的方式,虚拟节点比真实节点就会较均匀的分布在hash环上,当我们选择了某一个虚拟节点之后,再将虚拟节点映射到真实节点上即可,例如NodeA#2,映射到真实节点NodeA上,这样就可以避免数据倾斜的问题了。