16 集群
集群由多个节点(redis服务器)组成。多个节点之间通过握手进行连接,从而构造了集群。
CLUSTER MEET <ip> <port>
客户端向一个节点发送这条命令,可以使得接收命令的节点与命令中对应ip地址和端口的服务器节点进行握手,使得其成为集群中的一份子。
16.1 节点
16.1.1 启动节点
每个节点都是一个redis服务器,但除了具有redis服务器的功能与结构以外还有其他的特征,在cluster.h中分别有clusterNode,clusterLink,clusterState结构用于保存信息。
16.1.2 集群数据结构(clusterNode,clusterLink,clusterState)
clusterNode用于保存节点自身的信息;其中的link属性就是一个clusterLink结构,用于保存和其他及诶按进行交流所需的资源,包括连接的创建时间,套接字描述符,输入输出缓冲区,与其相连接的节点。
注:clusterLink结构类似redisClient结构,但clusterLink用于连接节点,redisClient结构用于连接客户端。
在每个节点(clusterNode)中都保存一个clusterState结构用于保存当前集群的信息,包括集群状态,集群中其他节点的信息(字典),集群的配置纪元等
CLUSTER MEET命令的实现
原理类似tcp中的三次握手
- 媒人牵线:客户端(媒人)向节点A(男主)发送命令
CLUSTER MEET <ip> <port>
,里面包含了节点B(女主)的信息。 - 主动认识:节点A通过ip和port(女主的联系方式)给节点B发送MEET(约会)消息
- 两情相悦:节点B收到节点A的MEET命令后,给节点A回复PONG(收到,ok)命令
- 牵手成功:节点A收到节点B的PONG之后,了解了B的心意,最后给节点B回复PING命令。ojbk,牵手成功。
16.2 槽指派
集群用分片的方式保存数据库中的键值对,数据库被分为16384个槽,每个键都是属于一个槽,每个节点复制其中任意多个槽。
只有所有槽都有对应节点负责的时候,集群才属于上线状态。
CLUSTER ADDSLOTS <slots>
命令可以给节点分配复制的槽。
16.2.1 记录节点的槽指派信息
每个节点(clusterNode)结构中的slots属性,是一个大小为16384/8大小的无符号char数组,使用位图法,每位对应一个槽,记录对应的槽是不是自己负责(位为1表示这个槽由自己负责)。而numslots属性记录负责的槽的数量。
16.2.2 传播节点的槽指派信息
每个节点会将自己的slots数组通过发消息发送给集群中的其他节点,告诉其他节点自己负责那些槽,方便其他节点同步更新自己clusterState结构中对应到这个节点的信息。
16.2.3 记录集群所有槽的指派信息
clusterState结构中有一个属性是slots,是一个大小为16384大小的clusterNode指针数组,用下标对应每个槽,他们都指向对应负责这个槽的节点。
通过这个节点可以在O(1)复杂度先找到对应槽的负责节点,如果没有这个结构,靠每个节点的slots数组(位图)也能找到,但复杂度为最坏O(n),n为节点数量。
而每个节点的slots数组用于发送给其他节点信息时使用,如果发送clusterState结构的slots数组,需要遍历筛选。
16.2.4 CLUSTER ADDSLOTS命令的实现
- 先检查命令中的所有槽是否都未被指派,若存在已被指派的,返回空
- 若都为被指派,则遍历每个槽对应更新clusterNode.slots和clusterState.slots。
16.3 在集群中执行命令
- 先检查发送的命令对应的键是不是自己负责的槽点(有一个跳跃表存在键对应的槽)
- 如果是,直接执行命令
- 如果不是,返回MOVED错误,并指引其到正确的节点(查clusterState.slots)
16.3.1 计算键属于哪个槽
CLUSTER KEYSLOT <key>
实现是利用键的校验和与整数16383进行与运算(CRC16(key)&16383
)
16.3.2 判断槽是否由当前节点负责处理
根据clusterState.slots数组,对应这个槽指向的节点,是当前节点就直接执行命令,不是就根据指针,引导到正确的节点(换个套接字进行工作)。
注:上述MOVED错误在集群状态下并不会打印到客户端,起引导到正确节点的作用,但在单机模式,服务器无法识别这个符号,会打印错误到客户端。
16.3.4 节点数据库的实现
用跳跃表来储存键和槽号的关系,跳跃表节点的分值为槽号,跳跃表节点的成员是键,即利用槽号进行了排序,又建立了槽号与键的关系。
16.4 重新分片
集群的重新分片操作是将任意多个已经指派给某个节点的槽改为指派到另一个节点(槽的所有键值对也会转移)。
重新分片过程中(键值对转移过程中),集群不需要下线,转移中的键和槽仍然可以被命令控制。
实现原理:转移方向:源节点—>目标节点
- 对目标节点发送
CLUSTER SETSLOT <slot> IMPORTING <source_id>
命令,告诉目标节点,你将从源节点导入那个槽的键值对。 - 给源节点发送
CLUSTER SETSLOT <slot> MIGRATE <target_id>
命令,告诉源节点,你要吧那个槽的键值对发到那个节点。 - 发送
CLUSTER SETSLOT <slot> <count>
命令,获取最多属于slot槽的count个键值对的键名 - 对应每个上一步获得的键名,都向源节点发送一个
MIGRATE <target_id> <target_port> <key_name> 0 <timeout>
命令,将被选中的键值对签约到目标节点。 - 重复上两步,将该槽中所有节点都转移。
- 向集群中任意一个节点发送
CLUSTER SETSLOT <slot> NODE <target_id>
,把槽指派给目标节点,最后指派信息会传播到整个集群中。
16.5 ASK错误
16.5.1 CLUSTER SETSLOT IMPORTING命令的实现
当前节点从其他节点导入时,在本身节点对应的clusterState结构中的import_slots_from数组,属性是clusterNode的指针,大小是16384,若不为空,表示指向的这个节点正在导入对应下标的槽,为空表示,此槽未在导入过程中。
16.5.2 CLUSTER SETSLOT MIGRATING命令的实现
当前节点正在迁移槽到其他节点时,在本身节点对应的clusterState结构中的migrating_slots_from数组,属性是clusterNode的指针,大小是16384,若不为空,表示正在将对应下标的槽导出到指向的这个节点,为空表示,此槽未在导出过程中。
16.5.3 ASK错误
迁移过程中在槽没被迁移走之前,发送与数据库键有关的操作,但键被迁走了,发送ASK错误,并引导到正确的节点。在引导到目标节点后执行对应命令之前会先执行一次ASKING命令。
16.5.4 ASKING命令
ASKING命令的作用是打开发送命令的客户端的REDIS_ASKING标识。
在节点执行命令时,对应的键如果分配过来了,但对应的槽没分配过来的话,节点不会执行这个命令,除非对应的客户端打开了REDIS_ASKING标识。
REDIS_ASKING标识是一次性的,打开后,并在执行后,这个标识会被关闭。
ASK和错误MOVED错误的区别:同样是找不到负责的数据,然后转向正确的节点进行处理,但MOVEN错误,是正常错误,直接转向,直接执行。而ASK错误是临时性的,在没打开ASKING标识的情况下,不会执行。
16.6 复制和故障转移
16.6.1 设置从节点
客户端利用命令CluSTER REPLICATE <node_id>
将接受命令的节点设置为对应参数的从节点。设置原理类似之前的复制。
16.6.2 故障检测
集群中每个主节点会不定期给其他主节点发送PING命令,在规定时间内未收到PONG命令则将这个节点标记为疑似下线。
并在自己的clusterState结构中找到这个节点的clusterNode结构将下线报告存到clusterNode.fail_reports属性中,fail_reports是一个链表。
集群中的节点会互相发消息报告各个节点的状态信息。若半数以上的主节点都将某个主节点标记为疑似下线,则这个主节点进入已下线状态,并在集群状态中发布消息告诉所有节点这个主节点已经下线。
16.6.3 故障转移
若主节点为已下线状态,就会从其从节点中选择一个从节点执行SLAVEOF no one
成为主节点,撤销下线主节点负责的槽指派,并将这些槽全部指派给自己,然后发送一条PONG消息到广播中,表示自己成为新的主节点。
16.6.4 选举新的主节点
当从节点收到消息,自己复制的主节点下线后,会发消息到集群广播中给自己拉票,其他主节点具有投票权,这些主节点会投票给它收到消息的第一个从节点,每次选举都会把配置纪元加一,有一个从节点得票过半就当选,没有从节点得票过半就从新再投一次票,配置纪元又需要加一。
16.7 消息
有以下种类的消息:
- meet消息:在客户端给节点发送CLUSTER MEET命令时,节点会给命令中提供参数所指定的节点发送meet消息。
- ping消息:节点每隔一秒钟就会随机选择五个节点,并向其中最长没发送消息的节点发送ping信息。在节点间无ping交流达到cluster-node-timeout选项设置的时长的一半时也会发送ping消息,用于更新其他节点对本身节点的认知。
- pong消息:用于作为meet消息和ping消息的回复。节点也会向集群广播发送消息用于刷新其他节点对自己的认知。
- fall消息:用于向集群广播宣布某个节点已经下线
- publish消息:向频道发布消息
每个消息分为消息头和消息正文。
16.7.1 消息头
消息头包裹着消息的正文,消息头记录了
- 消息的信息(长度,类型,消息正文的节点信息数量);
- 发送者的信息(配置纪元,名字,槽指派信息,集群信息,标识值,端口号,主从复制信息)
- 消息正文(使用联合体,不同类型的信息用同一个数据结构)
记录发送者的各种详细信息用于让接收者判断发送者的状态和角色是否发生了变化。
16.7.2 meet,ping,pong消息的实现
这三种消息都是用同样的消息正文,类型是使用消息正文来判断,并且从已知节点中随机选择两个保存到clusterMsgDataGossip结构中,这个结构记录了节点的信息(ip与端口号,标识符,最后一场发送ping消息和接收pong消息的时间戳),用于接收消息的节点更新对这两个节点的认识(不认识就进行握手)
16.7.3 fall消息的实现
散播节点下线消息,需要散播给所有节点,使用meet,ping,pong消息的实现在时间上不够高效。
散播下线消息的节点,会在集群广播中散播,散播消息的结构中只有下线节点的名字,收到的节点都会将这个名字对应的节点标记为下线
publish消息的实现
publish <channel> <message>
节点收到客户端发来这个消息的时候,不仅会向channel频道发送消息message,同时会把这条消息逐个散播到其他节点,这样其他节点也都会想channel频道发送消息messsage。
消息的结构为两个整数和一个字节数组,两个整数分别指明频道和消息这个两个参数在字节数组中的位置。