数据切片和实例的对应分布关系
官⽅提供了⼀个名为Redis Cluster的⽅案,⽤于实现切⽚集群。Redis Cluster⽅案中就规定了数据和实例的对应规则。
Redis Cluster⽅案采⽤哈希槽(Hash Slot,接下来我会直接称之为Slot),来处理数据和实例之间的映射关系。⼀个切⽚集群共有16384个哈希槽,这些哈希槽类似于数据分区,每个键值对都会根据它的key,被映射到⼀个哈希槽中。
🤔思考问题:
🥦 为什么有slot的概念?
- 当实例数量发生变化,又要rehash所有的数据,如果redis集群的数据量很大,数据的hash和rehash过程十分消耗时间的。
- 由于每个实例都会保存所有key与实例的对应关系,实例之间传播这些数据消大量耗时间,客户端需要保存每个key对的实例管理也占用内存。
所以Redis Cluster抽象出来一个slot的概念,固定16384个。
🥬 如果只是简单采用简单hash算法把slot分配到不通的redis实例时又会出现什么问题?
- 虽然只有固定的16384个hash槽,但是如果hash槽的占用内存比较大,rehash所有的slot也是比较耗费时间的。
所有基于以上问题,不能使用简单的hash算法,使用一致性hash算法是比较好的选择。(目的是解决:当加入新的节点,重新hash的槽能尽可能的少)
简单的说,一致性哈希是将整个哈希值空间组织成一个虚拟的圆环,圆圈中的节点管理一个范围,落在这个范围内的数据就归次节点管理。
数据A会被定为到Server 1上,数据B被定为到Server 2上,而C、D被定为到Server 3上。
当节点删除或者增加时,只会影响相邻节点的数据,具有较好的容错性和可扩展性
但是这样又会出现新的问题:如果节点较少,会出现数据分配不均匀,如何解决?—— 虚拟节点
具体做法可以在服务器IP或主机名的后面增加编号来实现,例如上面的情况,可以为每个服务节点增加三个虚拟节点,于是可以分为 RedisService1#1、 RedisService1#2、 RedisService1#3、 RedisService2#1、 RedisService2#2、 RedisService2#3,具体位置如下图所示:
客户端如何定位数据?
在定位键值对数据时,它所处的哈希槽是可以通过计算得到的,这个计算可以在客⼾端发送请求时来执⾏。但是,要进⼀步定位到实例,还需要知道哈希槽分布在哪个实例上。
- edis实例会把⾃⼰的哈希槽信息发给和它相连接的其它实例,来完成哈希槽分配信息的扩散。当实例之间相互连接后,每个实例就有所有哈希槽的映射关系了。
- 客⼾端收到哈希槽信息后,会把哈希槽信息缓存在本地。
🤔思考问题:
如果集群中有实例增减,这些数据是如何变化的?
实例之间还可以通过相互传递消息,获得最新的哈希槽分配信息,但是,客⼾端是⽆法主动感知这些变化的。这就会导致,它缓存的分配信息和最新的分配信息就不⼀致了,那该怎么办呢?
- Redis Cluster⽅案提供了⼀种重定向机制,当客⼾端给⼀个实例发送数据读写操作时,这个实例上并没有相应的数据,客⼾端要再给⼀个新实例发送操作命令。当客⼾端把⼀个键值对的操作请求发给⼀个实例时,如果这个实例上并没有这个键值对映射的哈希槽,那么,这个实例就会给客⼾端返回下⾯的MOVED命令响应结果,这个结果中就包含了新实例的访问地址。客户端会缓存这个信息到本地,下次访问时,直接去示例3寻找。
GET hello:key
(error) MOVED 13320 172.16.19.5:6379
如果此时的Slot2比较大,当正在迁移数据到实例3时,客户端发起了一个读取Solt2的请求,此时客户端会收到一个ASK报错信息
GET hello:key
(error) ASK 13320 172.16.19.5:6379
客⼾端请求的键值对所在的哈希槽13320,在172.16.19.5这个实例上,但是这个哈希槽正在迁移。此时,客⼾端需要先给172.16.19.5这个实例发送⼀个ASKING命令。这个命令的意思是,让这个实例允许执⾏客⼾端接下来发送的命令。然后,客⼾端再向这个实例发送GET命令,以读取数据。和MOVED命令不同,ASK命令并不会更新客⼾端缓存的哈希槽分配信息。