Redis集群是Redis 提供的分布式数据库方案,集群通过分片( sharding )来进行数据共享,并提供复制和故障转移功能。
节点
一个Redis集群通常由多个节点( node)组成,在刚开始的时候,每个节点都是相互独立的,它们都处于一个只包含自己的集群当中,要组建一个真正可工作的集群,我们必须将各个独立的节点连接起来,构成一个包含多个节点的集群。

启动节点
一个节点就是一个运行在集群模式下的Redis服务器,Redis服务器在启动时会根据cluster-enabled配置选项是否为yes来决定是否开启服务器的集群模式,如图17-6所示。

节点(运行在集群模式下的 Redis服务器)会继续使用所有在单机模式中使用的服务器组件。
集群数据结构P264
CLUSTER MEET命令的实现 P268
槽指派
Redis集群通过分片的方式来保存数据库中的键值对:集群的整个数据库被分为16384个槽( slot ),数据库中的每个键都属于这16384个槽的其中一个,集群中的每个节点可以处理0个或最多16384个槽。
当数据库中的16384个槽都有节点在处理时,集群处于上线状态(ok );相反地,如果数据库中有任何一个槽没有得到处理,那么集群处于下线状态( fail )。
记录节点的槽指派信息
clusterNode 结构的slots属性和numslot属性记录了节点负责处理哪些槽:

slots属性是一个二进制位数组( bit array),这个数组的长度为16384/8=2048个字节,共包含16384个二进制位。
Redis 以0为起始索引,16383为终止索引,对slots数组中的16384个二进制位进行编号,并根据索引i上的二进制位的值来判断节点是否负责处理槽i:
如果slots 数组在索引i上的二进制位的值为1,那么表示节点负责处理槽i
如果slots数组在索引i上的二进制位的值为0,那么表示节点不负责处理槽i。
传播节点的槽指派信息
一个节点除了会将自己负责处理的槽记录在clusterNode结构的slots属性和numslots属性之外,它还会将自己的slots数组通过消息发送给集群中的其他节点,以此来告知其他节点自己目前负责处理哪些槽。


记录集群所有槽的指派信息P272
CLUSTER ADDSLOTS命令的实现 P274

当客户端对17-16所示的节点执行命令:
CLUSTER ADDSLOTS 1 2
将槽1和槽⒉指派给节点之后,节点的clusterstate结构将被更新成图17-17所示的样子:
clusterstate.slots 数组在索引1和索引2上的指针指向了代表当前节点的
clusterNode 结构。
并且clusterNode.slots数组在索引1和索引2上的位被设置成了1。

在集群中执行命令
在对数据库中的16384个槽都进行了指派之后,集群就会进入上线状态,这时客户端就可以向集群中的节点发送数据命令了。
当客户端向节点发送与数据库键有关的命令时,接收命令的节点会计算出命令要处理的数据库键属于哪个槽,并检查这个槽是否指派给了自己:
如果键所在的槽正好就指派给了当前节点,那么节点直接执行这个命令。
如果键所在的槽并没有指派给当前节点,那么节点会向客户端返回一个MOVED错误,指引客户端转向(redirect)至正确的节点,并再次发送之前想要执行的命令。

计算键属于哪个槽
节点使用以下算法来计算给定键key属于哪个槽:

其中CRC16(key)语句用于计算键key的CRC-16校验和,而& 16383语句则用于计算出一个介于0至16383之间的整数作为键key的槽号。
使用CLUSTER KEYSLOT<key>命令可以查看一个给定键属于哪个槽。
判断槽是否由当前节点负责处理
当节点计算出键所属的槽i之后,节点就会检查自己在clusterstate.slots数组中的项i,判断键所在的槽是否由自己负责:
1)如果clusterstate.slots [i]等于clusterstate.myself,那么说明槽i由当前节点负责,节点可以执行客户端发送的命令。
2)如果clusterstate.slots [ i]不等于clusterstate.myself,那么说明槽i并非由当前节点负责,节点会根据clusterstate.slots[i]指向的clusterNode结构所记录的节点IP和端口号,向客户端返回MOVED错误,指引客户端转向至正在处理槽i的节点。

MOVED错误
当节点发现键所在的槽并非由自己负责处理的时候,节点就会向客户端返回一个MOVED错误,指引客户端转向至正在负责槽的节点。


节点数据库的实现
集群节点保存键值对以及键值对过期时间的方式,与第9章里面介绍的单机Redis服务器保存键值对以及键值对过期时间的方式完全相同。
节点和单机服务器在数据库方面的一个区别是,节点只能使用0号数据库,而单机Redis 服务器则没有这一限制。

slots_to_keys跳跃表每个节点的分值(score)都是一个槽号,而每个节点的成员( member)都是一个数据库键:
每当节点往数据库中添加一个新的键值对时,节点就会将这个键以及键的槽号关联到slots_to_keys跳跃表。
当节点删除数据库中的某个键值对时,节点就会在slots_to_keys跳跃表解除被删除键与槽号的关联。
通过在slots_to_keys跳跃表中记录各个数据库键所属的槽,节点可以很方便地对属于某个或某些槽的所有数据库键进行批量操作,例如命令CLUSTER GETKEYSINSLOT<slot><count>命令可以返回最多count个属于槽slot的数据库键,而这个命令就是通过遍历slots_to_keys跳跃表来实现的。
重新分片
Redis集群的重新分片操作可以将任意数量已经指派给某个节点(源节点)的槽改为指派给另一个节点(目标节点),并且相关槽所属的键值对也会从源节点被移动到目标节点。
重新分片操作可以在线( online)进行,在重新分片的过程中,集群不需要下线,并且源节点和目标节点都可以继续处理命令请求。
重新分片实现原理


ASK错误 P284

复制与故障转移

Redis集群中的节点分为主节点( master)和从节点( slave ),其中主节点用于处理槽,而从节点则用于复制某个主节点,并在被复制的主节点下线时,代替下线主节点继续处理命令请求。

如果这时,节点7000进入下线状态,那么集群中仍在正常运作的几个主节点将在节点7000的两个从节点——节点7004和节点7005中选出一个节点作为新的主节点,这个新的主节点将接管原来节点7000负责处理的槽,并继续处理客户端发送的命令请求。




设置从节点
故障检测
集群中的每个节点都会定期地向集群中的其他节点发送PING消息,以此来检测对方是否在线,如果接收PING消息的节点没有在规定的时间内,向发送PING消息的节点返回PONG消息,那么发送PING消息的节点就会将接收PING消息的节点标记为疑似下线( probable fail,PFAIL )。
故障转移
选举新的主节点
消息
消息头
MEET、PING、PONG消息的实现
FAIL消息的实现
PUBLISH消息的实现
重点
节点通过握手来将其他节点添加到自己所处的集群当中。
集群中的16384个槽可以分别指派给集群中的各个节点,每个节点都会记录哪些槽指派给了自己,而哪些槽又被指派给了其他节点。
节点在接到一个命令请求时,会先检查这个命令请求要处理的键所在的槽是否由自己负责,如果不是的话,节点将向客户端返回一个MOVED错误,MOVED错误携带的信息可以指引客户端转向至正在负责相关槽的节点。
对Redis集群的重新分片工作是由redis-trib负责执行的,重新分片的关键是将属于某个槽的所有键值对从一个节点转移至另一个节点。
如果节点A正在迁移槽i至节点B,那么当节点A没能在自己的数据库中找到命令指定的数据库键时,节点A会向客户端返回一个ASK错误,指引客户端到节点B继续查找指定的数据库键。
MOVED错误表示槽的负责权已经从一个节点转移到了另一个节点,而ASK错误只是两个节点在迁移槽的过程中使用的一种临时措施。
集群里的从节点用于复制主节点,并在主节点下线时,代替主节点继续处理命令请求。
集群中的节点通过发送和接收消息来进行通信,常见的消息包括MEET、PING、PONG、PUBL工SH、FAIL五种。