浅析一致性hash和hash槽

通过本文将收获如下:

  • 文章中有一些补充知识点,不想了解可以跳过
  • 为什么Redis Cluster的Hash Slot 是16384?
  • 什么是hash(概念)
  • 什么是一致性hash
  • 什么是hash slot
  • 一致性hash与hash slot区别

最近碰到一个redis面试题涉及到一致性hash和hash槽,刚好不太了解,在此总结一下。

一、先看面试题:为什么Redis Cluster的Hash Slot 是16384?

我们知道一致性hash算法是2的16次方,为什么hash slot是2的14次方呢?
作者antirez的回复


在redis节点发送心跳包时需要把所有的槽放到这个心跳包里,以便让节点知道当前集群信息,16384=16k,在发送心跳包时使用char进行bitmap压缩后是2k(2 * 8 (8 bit) * 1024(1k) = 16K),也就是说使用2k的空间创建了16k的槽数。 虽然使用CRC16算法最多可以分配65535(2^16-1)个槽位,65535=65k,压缩后就是8k(8 * 8 (8 bit) *1024(1k)=65K),也就是说需要需要8k的心跳包,作者认为这样做不太值得;并且一般情况下一个redis集群不会有超过1000个master节点,所以16k的槽位是个比较合适的选择。

看不懂先不急,我们先来大概了解以下知识点

什么是hash

Hash一般翻译做“散列”,就是把固定或任意长度的输入,通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,通常散列值的空间远小于输入的空间,不同的输入可能会散列成相同的输出,而且不可能从散列值来唯一确定输入值。简单而言Hash就是一种将固定或任意长度的消息压缩到某一固定长度的消息摘要的处理

补充知识,不想了解可以跳过

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

先看这段代码,这是hashmap的hash函数
代码解析:

  • 当key不为null时,返回key hash值异或key hash值无符号右移16位后的hash值

异或:相同为0(1^ 1= 0 、 0^0=0 ),不同为1 (1^0 = 1 )
int类型32位,右移16,就是将前16置为0
eg:00011000000100011111000101100000–>00000000000000000001100000010001
这样可以保留高16位数据特性,同时高16位异或低16位降低hash冲突、增加散列程度。但是当数小于216时,右移16位后全是0,(h = key.hashCode()) ^ (h >>> 16)等价与key.hashCode(),不会重新调整其值

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length; //获取table的长度,默认16
        //  table数组在(n - 1) & hash位置上有没有值
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);// 创建链表节点
        else ……
    }

(n - 1) & hash 等价于<hash的十进制数%(n - 1=16-1=15)>
15二进制1111,如果上述key hash值不进行异或key hash值无符号右移16位后的hash值,这样最后4位为0的hash值都会插入table数组的0位

hash算法的缺点,一致性hash算法诞生

在分布式缓存服务中,经常需要对服务进行节点添加和删除操作,我们希望的是节点添加和删除操作尽量减少数据-节点之间的映射关系更新(节点数的增加与减少对数据命中节点影响不大)
eg:我们使用的是哈希取模( hash(key)%nodes ) 算法作为路由策略

  • hash(key)=10,nodes=5,hash(key)%nodes=0,命中0 redis节点 set hello world
  • hash(key)=10,nodes=3,hash(key)%nodes=1,命中1 redis节点 get hello

结果发现每次同样key,在节点数变化后命中的节点不一样,导致造成大量的请求无法命中(访问正确的节点)从而导致缓存数据被重新加载,这样的结果就是重新hash做一次sharding。这种操作会导致服务在一定的时间不可用,而且每次扩缩容都会存在这个问题


基于上面的缺点提出了一种新的算法:一致性哈希。一致性哈希可以实现节点删除和添加只会影响一小部分数据的映射关系,由于这个特性哈希算法也常常用于各种均衡器中实现系统流量的平滑迁移

什么是一致性hash

一致性hash算法主要应用于分布式存储系统中,可以有效地解决分布式存储结构下普通余数Hash算法带来的伸缩性差的问题,可以保证在动态增加和删除节点的情况下尽量有多的请求命中原来的机器节点。

一致性hash是一个0-232的闭合圆,(拥有223个桶空间,每个桶里面可以存储很多数据,可以理解为s3的存储桶)所有节点存储的数据都是不一样的

一致性hash

看到这幅图不要怕,让我们看一下这幅图这些大小不一的⚪都是什么

  • 这个最大的⚪便是0-232的闭合圆,其中0位置与232重叠
  • 第二打的⚪且有节点字样的代表节点,通常使用其节点的ip或者是具有唯一标示的数据进行hash(ip),将其值分布在这个闭合圆上
  • 最小的⚪代表节点里的数据,通常将存储的key进行hash(key),然后将其值要分布在这个闭合圆上
  • 怎么区分最小的⚪是那个节点的?
    – 从hash(key)在圆上映射的位置开始顺时针方向找到的一个节点即为存储key的节点(节点逆时针的最小⚪都是该节点数据

一致性hash就是尽量在分布式节点增加、减少情况下,让请求命中原节点

一致性hash——增加节点
在这里插入图片描述

一致性hash——减少节点
在这里插入图片描述

通过增加、减少节点,只会影响一小部分数据的映射关系


到这里,小可爱你不会因为一致性hash结束了吧,天真哈,嘻嘻嘻

看看减少节点那幅图,你有没有发现,当节点一数据(热点数据)特别多,节点一宕机,数据传给节点二,但这样节点二数据突然暴增,请求突然暴增,然后也宕机了,就这样一种传下去,所有节点是不是都宕机了,这就是雪崩
当节点特别少,比如两个节点时,容易造成数据倾斜
在这里插入图片描述

为了解决上面两种问题,引入虚拟节点,定位算法不变,就是多了一步虚拟节点到真实节点映射的过程,这时真实节点不再放置到哈希环上,只有虚拟节点才会放上去

在这里插入图片描述

雪崩:当节点一宕机,节点的虚拟节点v1001与v1002消失,数据落在了节点三虚拟节点v3001和节点四虚拟节点v4001上,这样对节点压力减小
数据倾斜:虚拟节点增加,数据一直落在一个节点上的概率下降
到这里,一致性hash结束,让我们快乐的进入下一个知识点,什么是hash槽

什么是hash slot

Redis Cluster在设计中没有使用一致性哈希(Consistency Hashing),而是使用数据分片引入哈希槽(hash slot)来实现
一个 Redis Cluster包含16384(0~16383)个哈希槽(可以假设成盒子),存储在Redis Cluster中的所有键都会被映射到这些slot中,集群中的每个键都属于这16384个哈希槽中的一个。按照槽来进行分片,通过为每个节点指派不同数量的槽,可以控制不同节点负责的数据量和请求数
eg:当前集群有3个节点,槽默认是平均分的:

  • 节点 A (6381)包含 0 到 5499号哈希槽.
  • 节点 B (6382)包含5500 到 10999 号哈希槽.
  • 节点 C (6383)包含11000 到 16383号哈希槽

增加一个 master节点,就将其他 master 的 hash slot 移动部分过去
减少一个 master节点,就将它的 hash slot 移动到其他 master 上去
这样移动 hash slot 的成本是非常低的,并且将一个哈希槽从一个节点移动到另一个节点不会造成节点阻塞, 所以无论是添加新节点还是移除已存在节点, 又或者改变某个节点包含的哈希槽数量, 都不会造成集群下线
那hash slot是怎么分槽的呢?
集群使用公式slot=CRC16(key)/16384来计算key属于哪个槽,其中CRC16(key)语句用于计算key的CRC16 校验和

问题一:增加、减少节点,都会有槽移动到新节点,ip不一样了,那不会报错吗?

当客户端的key 经过hash运算,发现slot 槽位不在原节点的时候:

  • 如果是非集群方式连接,则直接报告错误给client,并告诉client slot 槽位在集群中那个IP的新master主机
  • 如果是集群方式连接,则将客户端重定向到正确的节点上

到这里,让我们一起来看文章开头的那个面试题

Redis Cluster的之所以使用Hash Slot 的16384(214),而不使用一致性hash的216
bitmap与CRC16请自行查阅资料

  • Redis的作者认为CRC16(key) mod 16384的效果已经不错了,虽然没有一致性hash灵活,但实现很简单,节点增删时处理起来也很方便
  • redis的一个节点的心跳信息中需要携带该节点的所有配置信息,而16K(214)大小的槽数量所需要耗费的内存为2K(2 * 8bit *1K=16K),但如果使用65K(216)个槽,这部分空间将达到8K(8 * 8bit *1K=65K),心跳信息就会很庞大
  • 你可能认为2k和8k差不多,但当节点非常多时,4倍差距将会放的很大,当然Redis集群中主节点的数量基本不可能超过1000个
  • Redis主节点的配置信息中,它所负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中,会对bitmap进行char压缩,但是如果bitmap的填充率slots / N很高的话,bitmap的压缩率就很低,所以N表示节点数,如果节点数很少,而哈希槽数量很多的话,bitmap的压缩率就很低。而16K个槽且主节点为1000的时候,是刚好比较合理的,既保证了每个节点有足够的哈希槽,又可以很好的利用bitmap
  • 选取了16384是因为CRC16会输出16bit的结果,可以看作是一个分布在0-216-1之间的数,redis的作者测试发现这个数对214求模的会将key在0-214-1之间分布得很均匀,因此选了这个值

一致性hash与hash slot区别

通过上面的分析,不难发现两种都是解决数据与节点之间的映射,简而言之,就是致力于斩断数据与节点之间的联系,虽然两者都实现了,但在数据分布均匀方面,hash slot比一致性hash显得更加均匀,因为slot数的选择一方面依赖于CRC16校验的16bit key值对214求模的会将key在0-214-1之间分布得很均匀,同时一致性hash是根据hash函数计算的值,在hash环上随机;

灵魂问题:一致性哈希–环的数据结构保存在哪里

  1. 在每个节点上?
  2. 部分在每个节点及其范围?
  3. 在单独的机器上作为负载平衡器?

网上收集到的资料:

  1. 中央协调点:使用专用机器保持一个环并充当中央负载平衡器,将请求路由到适当的节点。优点:非常简单的实现。这将非常适合大量节点(数据)的动态系统。缺点:可扩展性和可靠性差。稳定的分布式系统没有单一的故障。
  2. 没有协调的中心点——完全复制:每个节点都保留一个完整的环副本。适用于稳定的网络。此选项用于例如 Amazon Dynamo。优点:查询直接路由到适当的缓存服务器。缺点:加入(离开)环中的服务器需要通知环中的所有缓存服务器。
  3. 有协调的中心点——部分复制:每个节点保留环的部分副本。这个选项是CHORD算法的直接实现。就 DHT(分布式哈希表)而言,每个缓存机器都有其前置和后继,并且在接收到查询时检查它是否具有密钥。如果那台机器上没有这样的密钥,则使用映射函数来确定其邻居(后继和前任)中的哪个与该密钥的距离最小。然后它将查询转发给距离最小的邻居。该过程一直持续到当前缓存机器找到密钥并将其发回。优点:对于高度动态的更改,由于节点之间的闲聊开销很大,因此前一个选项不合适。因此,此选项是这种情况下的选择。缺点:没有消息的直接路由。

总结

通过一致性hash,你可以发现每次通过key取值时都是到hash环上找
而hash slot就是到slot(槽)中找
它们是不是降低了对机器的依赖,即使增加、减少节点,对请求操作数据没什么影响

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝桉未与

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值