相关概念
集群:由一个或多个节点组成的,是Redis提供的分布式数据库方案。集群通过分片来进行数据共享,并提供复制和故障转移功能。
节点:一个集群模式下的Redis服务器。
槽:整个集群数据库分为16384个槽
节点
- 节点通过握手和响应握手来组成一个集群
- 通过CLUSTER NODES 可以查看集群节点信息。
- 节点启动
Redis服务器在启动时根据 cluster-enable来决定是否开启集群模式。
相较于普通的服务器,节点会将集群模式下需要的数据保存在clusterNode、clusterLink、clusterState结构。typeof struct clusterState{ //指向当前节点的指针 clusterNode *myself; //集群当前配置纪元,用于实现故障转移 unit64_t currrentEpoch; //集群中至少处理这一个槽的节点的数量 int size; //集群节点名单 字典的键为节点的名字;字典的值为对应节点的clusterNode结构 dict *nodes; ... } clusterState struct clusterNode{ //创建节点的时间 mstime_t ctime; //节点名称,由40个十六进制字符组成 char name[REDIS_CLUSTER_NAMELEN]; //节点标识,记录节点角色及其所处状态 int flags; //节点当前配置纪元,用于实现故障转移 unit64_t configEpoch; //节点的IP地址 int ip[REDIS_IP_STR_LEN]; //节点端口号 int port; //保存连接所需的有关信息 clusterLink *link; ... } typeof struct clusterLink{ //连接的创建时间 mstime_t ctime; //TCP套接字描述符 int fd; //输出缓冲区,保存等待发送给其他节点的消息(message) sds sndbuf; //输入缓冲区,保存着从其他节点接收的消息 sds rcvbuf; //与这个节点相关联的节点,没有就为NULL struct clusterNode *node; }
- CLUSTER MEET命令的实现
向节点A发送CLUSTER MEET <B_ip> <B_port>
首先,节点A会为节点B创建一个clusterNode结构,并将该结构添加到clusterState.nodes字典中;
第二,节点A通过B_ip和B_port 向节点B发送一条MEET消息;
第三,节点B接收到信息,节点B会为节点A创建一个clusterNode结构,并将该结构添加到clusterState.nodes字典中;
第四,节点B向节点A返回PONG消息;
第五,节点A接收到PONF消息,通过这个消息知道节点B已经成功接受到了自己的MEET消息;
第六,节点A向节点B发送PING消息;
第七,节点B接收到PING消息,节点B知道节点A成功接收到了自己PONG消息;
握手成功--------------------
节点A会通过Gossip协议将节点B的信息传播给集群中的其他节点,其他节点与节点B握手,直到节点B被其他所有节点认识。
槽指派
- 集群的整个数据库被分为16384个槽(slot),集群中的每个节点都可以处理0个或16384个槽;数据库中的每个键都属于这16384个槽的其中一个。
- 所有槽都被节点处理,集群处于上线状态;只要有任何一个槽没有被节点处理,集群就处于下线状态;
- structNode结构信息
slots[16384/8]是一个长度为2048的二进制数组,且以0为起始索引,16384为终止索引,索引i表示第i个槽;节点如果处理槽i,则这个索引i的二进制位的值为1,否则为0;structNode{ // ... unsigned char slots[16384/8]; int numslots;//节点处理的槽的数量 // ... }
便于检查节点是否处理了某个槽,或者将某个槽分配给节点处理。 - 节点除了会更新自己的cluesterNode结构的slots属性和numslots属性,也会将自己的slots数组通过消息发送给集群中的其他节点,其他节点接收后,也会更新clusterState.nodes字典中对应的clusterNode结构中的slots数组。
- clusterState结构信息
这是一个clusterNode结构的数组。如果slots[i]指向NULL,则表示未分配给任何节点;否则会指向所分配的节点cluserNode结构。typeof struct clusterState{ // ... clusterNode *slots[16384]; // ... }
便于检查槽i是否已经被指派,或便于获取处理槽i的节点。 - 分派槽的命令
CLUSTER ADDSLOTS [slot … ]
例子:CLUSTER ADDSLOTS 1 2
将槽1、2分派给节点
第一步:将clusterState结构中的slots数组的索引1,2上的指针指向代表当前节点的clusterNode结构;
第二步:将clusterNode结构中的slots数组在索引1,2的二进制位置1;
第三步:将自己负责处理的槽信息通过发送消息告知集群中的其他节点,其他节点进行相应更新。
键与槽的关联
- 节点会通过计算来确定给定key属于哪个槽
- 获取到所属槽i后,通过检查clusterState结构中的slots数组对应的索引i上的指针是否指向的节点与myself指向的节点是否是同一个,如果是就表示这个槽是自己负责的,如果不是,节点会根据其指向的cluserterNode结构中的ip和端口号,向客户端返回MOVED错误,指引客户端转向正在处理槽i的节点。
- MOVED错误: MOVED ::在集群中,MOVED错误不会打印在客户端,而是客户端会根据返回的MOVED错误自动进行节点转向,并打印转向信息。
- clusterState结构维护了一个跳表来保存槽与键的关系
跳表的分值表示一个槽号,而每个节点的成员都是一个数据库键。typeof struct clusterState{ // . . . zskiplist *slots_to_keys // ... }
重新分片
重新分片时,集群不需要下线,并且源节点和目标节点可以继续处理命令请求。
redis-trib是Redis的集群管理软件。
ASK错误
- 源节点维护了一个clusterNode结构的数组,记录当前正在迁移至其他节点的槽
typeof struct clusterState{ ... clusterNode *migrating_slots_to[16384]; }
- 目标节点维护了一个clusterNode结构的数组,记录当前正在从其他节点导入的槽
typeof struct clusterState{ ... clusterNode *importing_slots_from[16384]; }
- ASK错误
但ASK错误同MOVED错误,会被隐藏。
假设节点“love”正在从节点7002迁移至节点7003,则在7002 执行命令get "love"时,会返回一个ASK错误。客户端获取后,执行以下操作:
一:根据返回的错误,转至目标节点7003
二:向7003发送ASKING命令
三:再次发送GET “love”命令
四:获得回复 - 关于ASKING 命令
ASKING命令会打开客户端的标识 :REDIS_ASKING。因为在客户端发送一个关于槽 i 的命令时,如果槽 i 没有被指派给当前节点,就会返回一个MOVED错误。但有一个特殊情况,就是这个槽正在被导入,且发送命令的客户端带有 REDIS_ASKING 标识,节点将破例执行这个关于槽 i 的命令。
但这个REDIS_ASKING标识是一个一次性标识,使用过一次后,就会被移除。
复制与故障转移
- 复制转移的clusterNode结构
struct clusterNode{
...
//如果是从节点,会指向其主节点
struct clusterNode *slaveof;
//如果是主节点,表示正在复制这个主节点的从节点数量
int numslaves;
//如果是主节点,这个数组的每一项都指向正在复制这个主节点的从节点clusterNode结构
struct clusterNode **slaves;
//一个链表,记录了所有其他节点对该节点的下线报告
list *fail_reports;
}
- 集群中的节点可以分为主节点和从节点,主节点处理槽,从节点复制主节点,并能够在主节点下线时代替主节点继续处理命令。
- 设置从节点:CLUSTER REPLICATE <node_id>
- 节点状态有三种:在线、疑似下线(PFAIL)、已下线(FALL)
- 判断疑似下线状态:
集群中的每个节点都会定期的像集群中的其他节点发送PING消息,以此来检测对方是否在线,如果超过规定的时间都没有收到某个节点返回的PONG消息,则发送PING消息的节点会将这个这个没有返回PONG消息的节点标记为疑似下线。
- 判断已下线:
集群中的各个节点会通过发送消息的方式交换集群中各个节点的状态信息。
当节点B收到节点A认为节点D疑似下线,节点B会在clusterState的nodes字典中找到节点D对应的clusterNode结构,并将节点A的下线报告添加到clusterNode结构的fail_reports链表中。struct clusterNodeFailReport{ //报告目标节点已经下线的节点 struct clusterNode *node; //最后一次从node节点收到下线报告的时间,用来检测下线报告是否过期 mstime_t time; }
当一个集群中,有一半以上负责处理槽的的主节点认为某个主节点x疑似下线,那么这个主节点x就会被标记为已下线,并将这一消息向集群进行广播,所有收到这条消息的节点都会立即将主节点x标记为已下线。 - 故障转移
首先在已下线的主节点所属的从节点中选取新的主节点(类似于选取领头Sentinel)
第二步,被选中的从节点会执行SLAVEOF no one命令,成为新的主节点。
第三步,新的主节点会撤销所有对已下线主节点的槽指派,并将这些槽指派给自己。
第四步,新的主节点会向集群广播一条PONG消息,让集群中其他节点知道这个节点已经变成主节点了。
最后,新的主节点开始接收和处理命令,故障转移成功。
新的主节点选取规则: