《Redis设计与实现》学习笔记-集群

Redis也可通过集群来实现分布式,通过分片进行数据共享,并提供复制和故障转移。当前Redis版本的集群功能还没有正式发布,目前只是一个不稳定的分支,据说快要正式发布了。

添加集群节点

服务器节点通过执行CLUSTER MEET <ip> <port>命令把指定的服务器添加到当前集群中,通过CLUSTER NODES来查询当前集群中的所有节点信息,当cluster-enabled配置选项设成yes时,说明该服务器开启了集群模式,开启集群模式的节点才能被添加到集群。
在执行serverCron函数时,集群节点比单击节点多执行一个clusterCron函数,redis使用clusterNode、clusterLink、clusterState等结构来记录集群信息。
CLUSTER MEET命令实现:

  • 在节点B向节点A发送CLUSTER MEET命令时,节点A服务器通过参数解析出目标服务器B的ip和端口。
  • A为指定的ip和端口节点B创建一个clusterNode结构并且添加到clusterState.nodes字典中,并且发送一条MEET消息到B。
  • B接收到A的MEET消息之后为A创建一个clusterNode结构并把它添加到clusterState.nodes字典中,并且回复PONG消息通知A已经收到了MEET消息。
  • A收到PONG消息之后回复一条PING通知B已经收到了PONG回复,至此握手结束。之后节点A将节点B的消息通过Gossip协议传播给集群中其它节点,让其它节点和B进行握手,最终节点B会被集群中所有节点认识。

键槽

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 <slot> <count>命令,返回最多count个属于slot的数据库键。
通过执行CLUSTER ADDSLOTS <slot>命令指派槽给当前节点,这个命令接受一个或多个槽作为参数,将所有输入的槽指派给接受该命令的节点负责,这个命令的实现如下:
1、检查参数中的槽是否存在已经被其它节点处理的,如果存在直接返回错误。
2、对i号槽,clusterState.slots[i]设成当前节点对应的clusterNode,并且更新clusterNode的slots数组。

集群中执行命令

1、如果键所在的槽被指派给了当前节点,节点直接执行命令。
2、如果键所在的槽没有指派给当前节点,节点给客户端返回一个MOVED错误,指引客户端转向正确的结点。
3、客户端根据MOVED命令带回的ip和port参数把命令发到目标节点重试。

重新分片

集群可以通过重新分片操作将任意已指派给某个节点的槽改为指向另外一个节点,重新分片操作通过集群管理软件redis-trib负责执行,步骤如下:
  1. redis-trib对目标节点发送CLUSTER SETSLOT <slot> IMPORTING <source_id>命令,让目标节点准备好从源节点导入属于槽slot的键值对。
  2. redis-trib对源节点发送CLUSTER SETSLOT <slot> MIGRATING <target_id>命令,让源节点准备好将属于槽slot的键值对迁移到目标节点。
  3. redis-trib向源节点发送CLUSTER GETKEYSINSLOT <slot> <count>命令,获得最多count个属于槽slot的键值对。
  4. 对步骤3获得的每个键名,redis-trib都想源节点发送一个MIGRATE <target_ip> <target_port> <key_name> 0 <timeout>命令,将被选中的键原子地从源节点迁移到目标节点。
  5. 重复步骤3、4,直到源节点保存的所有属于槽slot的键值对都被迁移至目标节点为止。
  6. redis-trib向集群中任意一个节点发送CLUSTER SETSLOT <slot> NODE <target_id>,将槽slot指派给目标节点,这一指派消息会通过消息发送至整个集群,最终集群中所有节点都会直到槽slot已经指派给了目标节点。
如果重新分片涉及到多个槽,那么在redis-trib上重复上面过程。
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错误

在重新分片的过程中,当客户端向源节点发送一个数据库键有关的命令时,并且键所在的槽正在被迁移:
  1. 源节点在本节点的数据库中查找指定的键,如果找到直接执行客户端发送的命令。
  2. 如果找不到,说明键已经被迁移到目标节点,源节点向客户端返回一个ASK错误,指引客户端转向目标节点。
  3. 客户端执行ASKING命令道目标节点,目标节点接收到ASKING命令之后会打开REDIS_ASKING标识。
  4. 客户端重新之前发送的命令道目标节点。
ASK和MOVED的区别:
  • MOVED错误表示当前节点不是当前键所在槽的处理节点,之后客户端每次遇到关于该槽的命令请求时应该直接将请求按发送至MOVED所指向的结点。
  • ASK错误是临时的,客户端遇到ASK错误之后,只是本次请求临时转向ASK指定的节点,下次请求时还是继续访问最开始访问的节点。

复制与故障转移

可以给集群中的主节点设置从节点,从节点复制主节点,当主节点下线之后,集群推选出一个从节点作为新的主节点来处理槽。通过执行CLUSTER REPLICATION <node_id>来设置从节点,当服务器接收到该命令之后,该节点让自己成为指定节点的从节点,并且开始对主节点进行复制:
  1. 接收到该命令的节点首先会在clusterState.nodes字典中找到node_id所对应的clusterNode结构,并将自己的clusterState.myself.slaveof指针指向这个结构,记录当前节点正在复制的主节点。
  2. 然后节点修改clusterState.myself.flags属性,关闭原本的REDIS_NODE_MASTER标识,打开REDIS_NODE_SLAVE标识,表示该节点已经变成了从节点。
  3. 根据clusterState.myself.slaveof指向的clusterNode结构保存的ip地址和端口号,对主节点进行复制,相当于对从节点发送了SLAVEOF <master_ip> <master_port>命令。

故障检测

集群中每个节点都会定期向集群中其它节点发送PING消息,以此来检查对方是否在线,如果接收PING消息的结点在规定的时间内没有返回PONG消息,那么发送PING消息的节点会将接收消息的PING节点标记为疑似下线(PFAIL),修改节点对应的clusterNode结构的flags属性,打开REDIS_NODE_PFAIL标识。之后节点A会把节点B的疑似下线消息传播给集群中其它节点,当结点C通过消息获知节点B进入疑似下线状态时,把节点A发送的节点B的下线报告记录到B对应的clusterNode结构的fail_reports链表中。当一个集群中半数以上负责处理槽的主节点都将某个主节x点报告为疑似下线时,那么这个主节点将会被标记为下线(FAIL),并发送一条节点x的FAIL消息通知其它主节点主节点x已下线。

故障转移

当集群检测到故障之后,进行故障转移,故障转移步骤:
  1. 从下线的主节点中的从节点中推选一个新的主节点,和Sentinel选举领头类似,采用Raft算法。
  2. 被选中的从节点执行SLAVEOF no one命令,成为新的主节点。
  3. 新的主节点撤销所有堆已下线主节点的槽指派,并将这些槽全部指派给自己。
  4. 新的主节点向集群中广播一条PONG消息,通知其它节点本节点已经由从节点变成了主节点。
  5. 新的主节点开始接收和自己负责处理的槽有关的命令,故障转移完成。

集群消息

集群中节点消息主要有五种:
  • MEET:将消息接收这添加到集群中。
  • PING:集群中每隔一秒会从已知节点列表中随机选出五个节点,然后对这五个中最长时间没有发送过PING消息的节点发送PING消息,以此来检测节点是否在线。此外,节点A最后以此收到节点B发送的PONG消息的时间距离当前时间超过了节点A的cluster-node-timeout选项设置时长的一半,那么节点A也会向节点B发送PING消息,防止节点A因为长时间没有随机选中节点B作为PING消息的发送对象而导致节点B的信息更新滞后。
  • PONG:MEET和PING的回复消息。另外一个节点也可以通过PONG消息通知集群中其它节点本节点已经由从节点升级成了主节点。
  • FAIL:当一个主节点A判断另一个主节点B已经进入FAIL状态时,节点A会向集群广播一条关于B的FAIL消息,所有收到该消息的节点都会把B标记为已下线。这时不使用Gossip协议的原因是Gossip协议会有延迟需要一段时间才能传播至整个集群,而当结点已下线时需要尽快完成故障转移。
  • PUBLISH:当结点收到一个PUBLISH命令时,节点会执行该命令,并向集群广播一条PUBLISH消息,所有接收到这条PUBLISH消息额节点都会执行相同的命令。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页