首先给个总结:
- 一致性哈希算法是通过将哈希空间分为1-2^32-1的圆环,我们把cache节点和我们的object对象数据hash运算以后分别映射到圆环上,然后会把对象的数据按照顺时针绑定到最近的一个cache节点,实现数据与节点的绑定,这样做的好处就是如果节点发生增删,都只影响临近的那个数据变更,可以最大程度上抑制了键的重新分布(生产中也就是指服务器缓存的重新分布)。
- 当然一致性哈希会存在哈希倾斜性的问题,可能会导致数据倾斜和缓存雪崩,我们可以引入虚拟节点,让虚拟节点哈希后得到真实节点,来解决这个问题。
背景
在了解一致性Hash算法之前,先来讨论一下Hash本身的特点。普通的Hash函数最大的作用是散列,或者说是将一系列在形式上具有相似性质的数据,打散成随机的、均匀分布的数据。
- 举个例子,我们有三个服务器0、1、2号服务器,Hash以后取余数,就分别存在对应的节点上。(有0是为了取余为0时能识别到)
- 现在我们有4个redis server节点,20个数据:
- 取余,分配节点
- 现在我们生产环境突然发现redis节点不够用了需要新增节点,或者我们发现redis集群负载非常低,我们可以删除一些节点来节省资源。
- 现在我们增加一个redis节点
- 我们发现扩容后原先的1、2、3、20四个数据仍然在原节点上,也就是说redis0只有20命中,redis1只有1命中,redis2只有2命中,redis3只有3命中,命中率时4/20=20%。这里由于数据量级太小,不是很明显,具体可以看下面的公式讲解。
一致性哈希
一致性Hash是一种特殊的Hash算法,由于其均衡性、持久性的映射特点,被广泛的应用于负载均衡领域,如nginx和memcached都采用了一致性Hash来作为集群负载均衡的方案。
- 首先我们来理解一个环形hash空间,通常hash算法都是将value映射到一个32位的key值当中,我们把数轴卷起来变为一个首尾相接的圆环,取值范围0 - 2^32-1,即代表能存放从0 到 2^32-1个数字的位置,然后把对象映射到环形hash空间
- 我们现在有四个Object对象,Object1、2、3、4,取hash以后映射到hash空间。
- 然后把cache也映射到同一个hash空间。对于cache的计算。一般可以使用cache机器的IP地址,机器名等作为hash输入,也可以引入更多因子,比如端口号。
- 已经把cache映射到hash环形空间中了,接下来需要考虑的就是如何把对象映射到cache上面。
- 在这个环形空间中,比如从key1出发顺时针,碰到的第一个cache节点时key A(也就是cache A),那么key1就要存到蓝色的key A节点中,由于两者hash都是固定的,因此key1存到cache A中是唯一并且确定的,这样就找到一个数据映射cache的方案。
- 因此key1、2、3、4均顺时针找到cache节点后:
- 如果我们需要删除或增加一个cache节点,传统的hash算法这种取模的方式对后台服务器造成巨大的冲击,很多缓存都没有命中,如果你的业务代码是穿透型的,那就会穿过cache直击DB,很容易把数据库搞垮。
- 接下来看看一致性hash算法的精髓,我们如果移除cacheB,那么key 4(Objecy 4)就会顺时针找到cache c,存取在cache c。我们可以看出,移除cache c并不会影响object2、3,影响的是cache c逆时针到object 4这段范围。影响范围很小。并不像上面那种取模方式变动很大。
- 添加cache的话:也只会影响添加的cache到逆时针的第一个Object:
Hash倾斜性:问题与优化
事实上cache可能分布没有那么均匀,就会导致A的负载很高。
数据倾斜
如果节点的数量很少,而hash环空间很大(一般是 0 ~ 2^32),直接进行一致性hash上去,大部分情况下节点在环上的位置会很不均匀,挤在某个很小的区域。最终对分布式缓存造成的影响就是,集群的每个实例上储存的缓存数据量不一致,会发生严重的数据(hash)倾斜。
缓存雪崩
如果每个节点在环上只有一个节点,那么可以想象,当某一集群从环中消失时,它原本所负责的任务将全部交由顺时针方向的下一个集群处理。例如,当cache0退出时,它原本所负责的缓存将全部交给cache1处理。这就意味着cache1的访问压力会瞬间增大。设想一下,如果group1因为压力过大而崩溃,那么更大的压力又会向cache2压过去,最终服务压力就像滚雪球一样越滚越大,最终导致雪崩。
--->
- 为了使对象尽可能均匀地映射到所有的缓存实例中(解决缓存实例分布不均匀的问题),引入虚拟节点的概念。虚拟节点其实为真实节点在hash空间中的复制品,一个真实节点可以对应多个虚拟节点。虚拟节点的hash求值可以在真实节点的求值基础上加入编号等信息 hash(realCacheKey#1) 、 hash(realCacheKey#2)。
-
通过虚拟节点的原理我们来解决hash倾斜性的问题:
我们对虚拟节点进行hash时,虚拟节点的hash倾斜性怎么解决?
这就要由hash算法来保证了,均匀分布是概率上的均匀,当虚拟节点足够时,就能保证大概均匀了。
缓存命中率及单一热点问题
一致性哈希解决的是某节点宕机后缓存失效的问题,只会导致相邻节点负载增加。但是因为宕机后需要重新从数据库读取,会导致此时缓存命中率下降及db压力增加。
也无法避免单一热点问题。某一数据被海量请求,不论怎么哈希,哈希环多大,数据只存在一个节点,早晚有被打垮的时候。此时的解决策略是每个节点主备或主主集群。
hash漂移
某个节点失效了,缓存都漂到下个节点了;然后一会它又恢复了,这时候它就有脏数据了。
解决办法一是每个节点引入集群。
不用集群想彻底解决这个问题,可能需要引入第三方健康检查组件,如Consul,发现节点不稳定立即删除下线。
-
一致性hash算法最大程度上抑制了键的重新分布(服务器缓存的重新分布),而且我们还可以采用虚拟节点的思想,比如我们每个实际节点都配置100-500个虚拟节点,这样就能抑制分布不均匀。
-
命中率公式:n是服务器台数,m是增加的服务器,因此随着分布式集群不断扩大,命中率会越来越高。