Redis也可通过集群来实现分布式,通过分片进行数据共享,并提供复制和故障转移。当前Redis版本的集群功能还没有正式发布,目前只是一个不稳定的分支,据说快要正式发布了。
添加集群节点
服务器节点通过执行CLUSTER MEET
在执行serverCron函数时,集群节点比单击节点多执行一个clusterCron函数,redis使用clusterNode、clusterLink、clusterState等结构来记录集群信息。
CLUSTER MEET命令实现:
键槽
Redis集群通过分片的方式来保存 数据库(集群节点只能使用0号数据库)中的键值对:整个数据库被分成16384(2的14次方)个槽,每个键都属于其中某个槽的一个,计算键所在的槽,通过计算键的CRC-16校验和,把校验和和16383做与运算。当所有槽都有节点处理时,集群处于上线状态(ok),否则处于下线状态(fail)。clusterNode结构中的slots属性记录了节点处理了哪些槽,numslot属性记录了节点处理槽的个数。slots是一个长度16384的二进制数组,数组中的第i为为1表示i号槽被当前节点处理,为0表示i号不是该节点处理,对该数组的取值和设值复杂度都是O(1)。节点会把slots数组传播给集群中其它节点告知当前节点负责处理的槽。
clusterState.slots记录了集群中所有槽的指派信息,该数组是一个存储clusterNode指针的数组如果slots[i]指向NULL说明该槽尚未被指派给任何节点,如果指向一个clusterNode结构,说明i号槽被指派给了当前clusterNode结构代表的节点。
同时存储clusterNode.slots和clusterState.slots的原因是为了提高某些场景的查询效率,如果不存储clusterNode.slots,那么在把当前节点所处理的槽传播给其它节点时只能挨个遍历clusterState.slots,同样地,如果不存储clusterState.slots,当想知道某个槽被那个节点处理时需要遍历clusterState.nodes所有结构并检查它们的slots数组。
同时clusterState结构中有一个跳跃表属性slots_to_keys来保存槽和键的映射关系,通过这个属性可以很方便实现CLUSTER GETKEYSINSLOT
通过执行CLUSTER ADDSLOTS
1、检查参数中的槽是否存在已经被其它节点处理的,如果存在直接返回错误。
2、对i号槽,clusterState.slots[i]设成当前节点对应的clusterNode,并且更新clusterNode的slots数组。
集群中执行命令
1、如果键所在的槽被指派给了当前节点,节点直接执行命令。2、如果键所在的槽没有指派给当前节点,节点给客户端返回一个MOVED错误,指引客户端转向正确的结点。
3、客户端根据MOVED命令带回的ip和port参数把命令发到目标节点重试。
重新分片
集群可以通过重新分片操作将任意已指派给某个节点的槽改为指向另外一个节点,重新分片操作通过集群管理软件redis-trib负责执行,步骤如下:redis-trib对目标节点发送CLUSTER SETSLOT
clusterState结构中定义了clusterNode* importing_slots_from[16384]记录当前节点正在从其它节点导入的槽,如果importing_slots_from[i]为不为NULL,表示i号槽正在被当前节点导入,它指向的clusterNode代表i号槽的源节点。
clusterState结构中的clusterNode* migrating_slots_to[16384]数组记录了当前节点正在迁移至其它节点的槽,如果migrating_slots_to[i]不为NULL,说明当前节点正在迁移i号槽到目标节点,指向的clusterNode结构代表了目标节点。
ASK错误
在重新分片的过程中,当客户端向源节点发送一个数据库键有关的命令时,并且键所在的槽正在被迁移:源节点在本节点的数据库中查找指定的键,如果找到直接执行客户端发送的命令。如果找不到,说明键已经被迁移到目标节点,源节点向客户端返回一个ASK错误,指引客户端转向目标节点。客户端执行ASKING命令道目标节点,目标节点接收到ASKING命令之后会打开REDIS_ASKING标识。客户端重新之前发送的命令道目标节点。 ASK和MOVED的区别:
MOVED错误表示当前节点不是当前键所在槽的处理节点,之后客户端每次遇到关于该槽的命令请求时应该直接将请求按发送至MOVED所指向的结点。ASK错误是临时的,客户端遇到ASK错误之后,只是本次请求临时转向ASK指定的节点,下次请求时还是继续访问最开始访问的节点。