在看Redis的一些底层数据结构实现时,想到一个以前没有关注过的问题,就是Redis是如何把数据分配到集群中的每一个节点的。我们知道Redis用Cluster模式部署下,数据是分布式存储的,假设客户端随机请求到集群中的一台Redis服务,而数据不应该存储在这台机器时,Redis是怎么知道找到应该的存储数据的节点。
哈希槽
Redis集群用的是哈希槽,而不是简单哈希和一致性哈希。一个哈希槽相当于一个Redis实例里的一段可存多个key的地址段,好似是划了一个硬盘分区。他比db要小,比一个key要大(可以把哈希槽理解为Mysql的一个表分区)。槽位的转移,即为槽位ID不变,而是槽位地址变了。集群只需要记住槽位ID和槽位地址映射就可以了。crc算法计算一个key的结果是一个恒定的槽位ID,即使是槽位发生了迁移,集群根据槽位ID还是可以找到槽位新地址的。
redis服务如何判断key对应的槽道号,是否由本节点管理?
每一个节点都有一个属于自己的16384位的二进制位序列,每一位对应一个槽道号,当该二进制序列的某一位为1时,代表当前结点对该槽道号具有管理权。还有一个公有的大家都一样的由16384个元素组成的索引数组,跟上面的字符数组一样,还是每一位对应一个槽道号,但是数组中存储的信息是该槽道号的实际管理者。
当某一个结点收到 set key value 请求时,首先查看自己私有的二进制位序列,判断该key对应的槽道号是否归属自己管理,如果不归自己管理,则查看公有的大家都是一样内容的索引数组,根据槽道号找到找到该槽道号真正的管理者,并将信息转交给其真正的管理者进行保存。
在Redis中,把一个key-value键值对放入的最简单的方式就是set key value,如下所示:
127.0.0.1:7000> set key value
-> Redirected to slot [12539] located at 192.168.39.153:7002
OK
192.168.39.153:7002> get key
"value"
192.168.39.153:7002>
可以看出,当我们把key的值设置成为value的时候,客户端被重定向到了另一个节点192.168.39.153:7002,这是因为key对应的槽位是12359,所以我们的key-value就被放到了槽12359对应的节点,192.168.39.153:7002了。
哈希槽数量为什么是16384(2^14)个?65536 不可以吗?
这个问题,Redis 官方 Issues 也有朋友提出来过,地址:https://github.com/redis/redis/issues/2576。antirez 大神对这个问题做了回复,简单归纳起来,有以下原因
- 正常的心跳数据包携带节点的完整配置,它能以幂等方式来更新配置。如果采用 16384 个插槽,占空间 2KB (16384/8);如果采用 65536 个插槽,占空间 8KB (65536/8)
- Redis Cluster 不太可能扩展到超过 1000 个主节点,太多可能导致网络拥堵。
- 16384 个插槽范围比较合适,当集群扩展到1000个节点时,也能确保每个master节点有足够的插槽
8KB 的心跳包看似不大,但是这个是心跳包每秒都要将本节点的信息同步给集群其他节点。比起 16384 个插槽,头大小增加了4倍,ping消息的消息头太大了,浪费带宽。
Redis的数据结构
在crc算法找到哈希槽后,会存在多个Key落在同一个哈希槽中,那么必然会出现哈希冲突,Redis解决哈希冲突的方法和Java中HashMap的方法一样,采用拉链法解决。即在一个哈希槽中所有的Key-Value通过HashTable的数据结构存储,这样能快速定位到具体的Key。结构如下图
一个哈希表,是一个数组,数组的每个元素称为一个哈希桶。每个哈希桶中保存了键值对数据,哈希桶中的元素保存的并不是值本身,而是指向具体值的指针。