REDIS集群模式介绍
reids集群是redis提供的分布式数据库方案,集群通过 分片来进行数据共享,并提供 复制和故障转移功能
1.1 节点
一个redis集群通常由多个节点组成,在刚开始的时候,每个节点都是相互独立的,他们都处于一个只包含自己的集群当中,要组件一个真正可工作的集群,我们必须将各个独立的节点连接起来,构成一个包含多个节点的集群。
连接各个节点的工作可以使用cluster meet
命令来完成,格式:
cluster meet <ip> <port>
向一个节点node发送cluster meet
命令,可以让node节点与ip与port所指定的节点进行握手,当握手成功时,node节点就会将ip和port所指定的节点添加到node节点当前所在的集群中
1.1.1 启动节点
一个节点就是一个运行在集群模式下的redis服务器,redis服务器在启动时会根据cluster-enabled
配置选项是否为yes
为决定是否开启服务器的集群模式。
节点会继续使用所有在单机模式中使用的服务器组件。比如说:
- 节点会继续使用文件事件处理器来处理命令请求和返回命令回复
- 节点会继续使用时间事件处理器来执行serverCron函数,而serverCron函数又会调用集群模式特有的clusterCron函数。clusterCron函数负责执行在集群模式下需要执行的常规操作
- 节点会继续使用数据库来保存键值对数据,键值对依然会是各种不同类型的对象
- 节点会继续使用RDB持久化和AOF持久化模块来执行持久化工作
- 节点会继续使用发布与订阅模块来执行PUBLISH,SUBSCRIBE等命令
- 节点会继续使用复制模块来进行节点的复制工作
- 节点会继续使用Lua脚本环境来执行客户端输入的Lua脚本
除此之外,节点会继续使用redisServer结构来保存服务器的状态,使用redisClient结构来保存客户端的状态。
1.1.2 集群数据结构
clusterNode
结构保存了一个节点的当前状态,并为集群中的所有其他节点都创建一个相应的clusterNode结构,以此来记录其他节点的状态:
struct clusterNode{
// 创建节点的时间
mstime_t ctime;
// 节点的名称,由40个十六进制字符组成
char name[REDIS_CLUSTER_NAMELEN];
// 节点标识
// 使用各种不同的标识值记录节点的角色
// 以及节点目前所处的状态
int flags;
// 节点当前的配置纪元,用于实现故障转移
uint64_t configEpoch;
// 节点的IP地址
char ip[REDIS_IP_STR_LEN];
// 节点的端口号
int port;
// 保存连接节点所需的有关信息
clusterLink *link;
};
clusterNode结构的link
属性是一个clusterLink结构,该结构保存了连接节点所需的有关信息:
typedef struct clusterLink{
// 连接的创建时间
mstime_t ctime;
// TCP套接字描述
int fd;
// 输出缓冲区,保存着等待发送给其他节点的信息
sds sndbuf;
// 输入缓冲区,保存着其他节点接受到的信息
sds rcvbuf;
// 与这个链接相关联的节点,如果没有的话就是为Null
struct clusterNode *node;
}clusterLink;
最后,每个节点都保存着一个clusterState结构,这个结构记录了在当前节点的视角下,集群目前所处的状态:
typedef struct clusterState{
// 指向当前节点的指针
clusterNode *myself;
// 集群当前的配置纪元,用于实现故障转移
uint64_t currentEpoch;
// 集群当前的状态,是在线还是下线
int state;
// 集群中至少处理着一个槽的节点的数量
int size;
// 集群节点名单
// 字典的键为节点的名称,字典的值为节点对应的clusterNode结构
dict *nodes;
}clusterState;
- 结构的currentEpoch属性的值为0,表示集群当前的配置纪元为0
- 结构的size属性的值为0,表示集群目前没有任何节点在处理槽,因此结构的state属性的值为
REDIS_CLUSTER_FAIL
,这表示集群目前处于下线状态 - 结构的nodes字典记录了集群目前包含的是三个节点,这三个节点分别由三个clusterNode结构表示。
- 三个节点的clusterNode结构的flags属性都是REDIS_NODE_MASTER,说明三个节点都是主节点
1.1.3 CLUSTER MEET命令的实现
通过向节点A发送CLUSTER MEET命令,客户端可以让接收命令的节点A将另一个节点B添加到节点A当前所在的集群里面:
cluster meet <ip> <port>
收到命令的节点A将与节点B进行握手,以此来确认彼此的存在,并为将来的进一步通信打好基础:
- 节点A会为节点B创建一个clusterNode结构,并将该结构添加到自己的
clusterState.nodes
字典里面 - 之后,节点A将根据CLUSTER MEET命令给定的IP地址和端口号,向节点B发送一条MEET消息
- 如果一切顺利,节点B将接收到节点A发送的MEET消息,节点B会为节点A创建一个clusterNode结构,并将该结构添加到自己的cluserState.nodes字典里面。
- 之后,节点B将向节点A返回一条PONG消息
- 如果一切顺利,节点A将接收到节点B返回的PONG消息,通过这条PONG消息节点A可以知道节点B已经成功地接收到了自己发送的MEET消息
- 之后,节点A将向节点B返回一条PING消息
- 如果一切顺利,节点B将受到节点A返回的PING消息,通过这条PING消息节点B可以知道节点A已经成功地接收到了自己返回的PONG消息,握手完成
1.2 槽指派
redis集群通过分片的方式来保存数据库中的键值对:集群的整个数据库被分为16384个槽(slot),数据库中的每个键都属于这16384个槽的其中一个,集群中的每个节点可以处理0个或者最多16384个槽。
当数据库中的16384个槽都有节点在处理时,集群处于上线状态(ok);相反地,如果数据库中有任何一个槽没有得到处理,那么集群处于下线状态(fail)。
通过向节点发送CLUSTER ADDSLOTS
命令,我们可以将一个或多个槽指派给节点负责:
cluster addslots <slot> [slot...]
1.2.1 记录节点的槽指派信息
clusterNode结构的slots
属性和numslot
属性记录了节点负责处理那些槽:
struct clusterNode{
unsigned char slots[16384/8];
int numslots;
};
slots属性是一个二进制位数组,这个数组的长度为16384/8=2048个字节,共包含16384个二进制位。
redis以0为起始索引,16383为终止索引,对slots数组中的16384个二进制位进行编号,并根据索引i是哪个的二进制位的值来判断节点是否负责处理槽i
:
- 如果slots数组在索引
i
上的二进制位为1.那么表示节点负责处理槽i
- 如果slots数组在索引
i
上的二进制位的值为0,那么表示节点不处理槽i
因为取出和设置slots数组中的任意一个二进制位的复杂度仅为O(1),所以对于一个给定节点的slots数组来说,程序检查节点是否负责处理某个槽,又或者将某个槽指派给节点负责,这两个动作的复杂度都是O(1)
至于numslots属性则记录节点负责处理的槽的数量,也即是slots数组中值为1的二进制位的数量
1.2.2 传播节点的槽指派信息
一个节点除了将自己负责处理的槽记录在clusterNode结构的slots属性和numslots属性之外,它还会将自己的slots数组通过消息发送给集群中的其他节点,以此来告知其他节点自己目前负责处理那些槽。
当节点A通过消息从节点B那里接收到节点B的slots数组时,节点A会在自己的clusterState.nodes
字典中查找节点B对应的clusterNode结构,并对结构中的slots数组进行保存或者更新
1.2.3 记录集群所有槽的指派信息
clusterState结构中的slots数组则记录了集群中所有16384个槽的指派信息:
typedef struct clusterState{
clusterNode *slots[16384];
}clusterState;
slots数组包含16384个项,每个数组项都是一个指向clusterNode结构的指针:
- 如果
slots[i]
指针指向NULL,那么表示槽i尚未指派给任何节点 - 如果
slots[i]
指针指向一个clusterNode结构,那么表示槽i已经指派给了clusterNode结构所代表的节点
1.2.4 cluster addslots命令的实现
cluster addslots命令接受一个或多个槽作为参数,并将所有输入的槽指派给接收该命令的节点负责:
最后,在cluster addslots命令执行完毕之后,节点会通过发送消息告知集群中的其他节点,自己目前正在负责处理那些槽
1.3 在集群中执行命令
在对数据库的16384个槽都进行了指派之后,集群就会进入上线状态,这时客户端就可以向集群中的节点发送数据命令了。
当客户端向节点发送与数据库键有关的命令时,接受命令的节点会计算出命令要处理的数据库键属于哪个槽,并检查这个槽是否指派给了自己:
- 如果键所在的槽正好就指派给了当前节点,那么节点直接执行这个命令
- 如果键所在的槽并没有指派给当前节点,那么节点会向客户端返回一个MOVED错误,指引客户端转向至正确的节点,并再次发送之前想要执行的命令
1.3.1 计算键属于哪个槽
节点使用以下算法来计算给定键key属于哪个槽:
def slot_number(key):
return CRC16(key) & 16383;
其中CRC16(key)语句用于计算键key的CRC-16校验和,而&16383语句则用于计算出一个介于0与16383之间的整数作为键key的槽号。
使用cluster keyslot <key>
命令可以查看一个键属于哪个槽
1.3.2 判断槽是否由当前节点负责处理
当节点计算出键所属的槽i之后,节点就会检查自己在clusterState.slots
数组中的项i,判断键所在的槽是否由自己负责:
- 如果
clusterState.slots[i]
等于clusterState.myself
,那么说明槽i由当前节点负责,及诶单可以执行客户端发送的命令 - 如果
clusterState.slots[i]
不等于,那么说明槽i并非当前节点负责,节点会根据clusterState.slots[i]指向的clusterNode结构所记录的节点IP和端口号,向客户端发送MOVED错误,指引客户端转向正在处理槽i的节点
1.3.3 MOVED错误
当节点发现键所在的槽并非由自己负责处理的时候,节点就会向客户端返回一个MOVED错误,指引客户端转向正在负责槽的节点。
MOVE错误的格式为:moved <slot> <ip>:<port>
其中slot为键所在的槽,而ip和port则是负责处理槽slot 的节点ip和端口号。
一个集群客户端通常会与集群中的多个节点创建套接字连接,而所谓的节点转向实际上就是换一个套接字来发送命令。
如果客户端尚未与想要转向的节点创建套接字连接,那么客户端会先根据MOVED错误提供的IP地址和端口号来连接节点,然后再进行转向
1.3.4 节点数据库的实现
集群节点保存键值对以及键值对过期时间的方式,与单机redis服务器完全相同。
节点和单机服务器在数据库方面的一个区别是,节点只能使用0号数据库,而单机redis服务器没有这一限制
另外, 除了将键值对保存在数据库里面之外,节点还会用clusterState结构中的slots_to_keys跳跃表来保存槽和键的关系:
typedef struct clusterState{
zskiplist *slots_to_keys;
}clusterState;
slots_to_keys跳跃表每个节点的分值都是一个槽号,而每个节点的成员都是一个数据库键:
- 每当节点往数据库中添加一个新的键值对时,节点就会将这个键以及键的槽号关联到slots_to_keys跳跃表
- 当节点删除数据库中的某个键值对时,节点就会在slots_to_keys跳跃表解除被删除键与槽号的关联
1.4 重新分片
redis集群的重新分片操作可以将任意数量已经指派给某个节点的槽改为指派给另一个节点,并且相关槽所属的键值对也会从源节点被移动到目标节点。
重新分片操作可以在线进行,在重新分片的过程中,集群不需要下线,并且源节点和目标节点都可以继续处理命令请求。
重新分片的实现原理
redis集群的重新分片操作是由redis集群管理软件redis-trib负责执行的,redis提供了进行重新分片所需的所有命令,而redis-trib则通过向源节点和目标节点发送命令来进行重新分片操作
redis-trib对集群的单个槽slot进行重新分片的步骤如下:
- redis-trib对目标节点发送
cluster setslot <slot> importing <source_id>
命令,让目标节点准备好从源节点导入属于槽slot的键值对 - redis-trib对源节点发送
cluster setslot <slot> migrating <target_id>
命令,让源节点准备好将属于槽slot的键值对迁移至目标节点 - redis-trib 向源节点发送
CLUSTER GETKEYSINSLOT <slot> <count>
命令,获得最多count个属于槽slot的键值对的键名( key name )。 - 对于步骤3获得的每个键名,redis-trib都向源节点发送一个
MIGRATE <target_ ip> <target_ port> <key_ name> 0 <timeout>
命令,将被选中的键原子地从源节点迁移至目标节点。 - 重复执行步骤3和步骤4,直到源节点保存的所有属于槽slot的键值对都被迁移至目标节点为止。每次迁移键的过程如图17-24所示。
- redis-trib向集群中的任意一个节点发送
CLUSTER SETSLOT <slot> NODE <target_ id>
命令,将槽slot指派给目标节点,这一指派信息会通过消息发送至整个集群,最终集群中的所有节点都会知道槽slot已经指派给了目标节点。
1.5 ASK错误
在进行重新分片期间,源节点向目标节点迁移一个槽的过程中,可能会出现这样一种情况:属于被迁移槽的一部分键值对保存在源节点里面,而另一部分键值对则保存在目标节点里面。
当客户端向源节点发送一个与数据库键有关的命令,并且命令要处理的数据库键恰好就属于正在被迁移的槽时:
- 源节点会先在自己的数据库里面查找指定的键,如果找到的话,就直接执行客户端发送的命令
- 相反的, 如果源节点没能在自己的数据库里面找到指定的键,那么这个键有可能已经被迁移到了目标节点,源节点将向客户端返回一个ASK错误,指引客户端转向正在导入槽的目标节点,并再次发送之前想要执行的命令
1.5.1 cluster setslot importing命令的实现
clusterState结构的import_slots_from数组记录了当前节点正在从其他节点导入的槽:
typedef struct clusterState{
clusterState *import_slots_from[16384];
}clusterState;
如果importing_slots_from[i]的值不为NULL,而是指向一个clusterNode结构,那么表示当前节点正在从clusterNode所代表的节点导入槽i
在对集群进行重新分片时,向目标节点发送:
cluster setslot <i> importing <source_id>
可以将目标节点clusterState.importing_slots_from[i]的值设置为source_id所代表节点的clusterNode结构。
1.5.2 CLUSTER SELSLOT MIGRATING命令的实现
clusterState结构的migrationg_slots_to数组记录了当前节点正在迁移至其他节点的槽:
typedef struct clusterState{
clusterNode *migrating_slots_to[16384];
}clusterState;
如果migrating_slots_to[i]的值不为NULL,而是指向一个clusterNode结构,那么表示当前节点正在槽i迁移至clusterNode所代表的节点
1.5.3 ASK错误
1.5.4 ASKING命令
在一般情况下,如果客户端向节点发送一个关于槽i的命令,而槽i又没有指派给这个节点的话,那么节点将向客户端返回一个MOVED错误;但是,如果节点的clusterState. importing_ slots_ from[i] 显示节点正在导人槽i,并且发送命令的客户端带有REDIS_ ASKING 标识,那么节点将破例执行这个关于槽i的命令一次
当客户端接收到ASK错误并转向至正在导人槽的节点时,客户端会先向节点发送一个ASKING命令,然后才重新发送想要执行的命令,这是因为如果客户端不发送ASKING命令,而直接发送想要执行的命令的话,那么客户端发送的命令将被节点拒绝执行,并返回MOVED错误。
另外要注意的是,客户端的REDIS_ ASKING 标识是一个一次性标识,当节点执行了一个带有REDIS_ ASKING 标识的客户端发送的命令之后,客户端的REDIS_ ASKING 标识就会被移除。
1.5.5 ASK错误和MOVED错误的区别
ASK错误和MOVED错误都会导致客户端转向,他们的区别在于:
- MOVED错误代表槽的负责权已经从一个节点转移到了另一个节点:在客户端收到关于槽i的MOVED错误之后,客户端每次遇到关于槽i的命令请求时,都可以直接将命令请求发送至MOVED错误所指向的节点,因为该节点就是目前负责槽i的节点。
- 与此相反,ASK错误只是两个节点在迁移槽的过程中使用的一.种临时措施:在客户端收到关于槽i的ASK错误之后,客户端只会在接下来的一次命令请求中将关于槽i的命令请求发送至ASK错误所指示的节点,但这种转向不会对客户端今后发送关于槽i的命令请求产生任何影响,客户端仍然会将关于槽i的命令请求发送至目前负责处理槽i的节点,除非ASK错误再次出现。
1.6 复制与故障转移
redis集群中的节点分为主节点和从节点,其中主节点用于处理槽,而从节点则用于复制某个主节点,并在被复制的主节点下线后,代替下线主节点继续处理命令请求
1.6.1 设置从节点
向一个节点发送命令:cluster replicate <node_id>
可以让接收命令的节点称为node_id所指定节点的从节点,并开始对主节点进行复制:
- 接收到该命令的节点首先会在自己的clusterState.nodes字典中找到node_id所对应节点的clusterNode结构,并将自己的clusterState.myself.slaveof指针指向这个结构,以此来记录这个节点正在复制的主节点
- 然后节点会修改自己在clusterState.myself.flags中的属性,关闭原本的REDIS_node_master标识,打开redis_node_slave标识,表示这个节点已经由原来的主节点变成了从节点
- 最后,节点会调用复制代码,并根据clusterState.myself.slaveof指向的clusterNode结构所保存的IP地址和端口号,对主节点进行复制。因为节点的复制功能和单机Redis服务器的复制功能使用了相同的代码,所以让从节点复制主节点相当于向从节点发送命令
SLAVEOF <master_ ip> <master_ port>
。
1.6.2 故障检测
集群中的每个节点都会定期地向集群中的其他节点发送PING消息,以此来检测对方是否在线。如果接受PING消息的节点没有在规定的时间内,向方发送PING消息的节点返回PONG消息,那么发送PING消息的节点就会将接收PING消息的节点标记为疑似下线。
集群中的各个节点会通过互相发送消息的方式来交换集群中各个节点的状态信息,例如某个节点是处于在线状态、疑似下线状态( PFAIL),还是已下线状态( FAIL )。
当标记下线数量已经超过了半数,则标记为已下线,并向集群广播FAIL消息
1.6.3 故障转移
当一个从节点发现自己正在复制的主节点进入了已下线状态,从节点将开始对下线主节点进行故障转移,以下是步骤:
- 复制下线主节点的所有从节点里面,会有一个从节点被选中
- 被选中的从节点会执行salveof no one节点,成为新的主节点
- 新的主节点会撤销所有对已下线主节点的槽指派,并将这些槽全部指派给自己
- 新的主节点向集群广播一条pong消息,这条pong消息可以让集群中的其他节点立即知道这个节点已经由从节点变成了主节点,并且这个主节点已经接管了原本由已下线节点负责处理的槽
- 新的主节点开始接受和自己负责处理的槽有关的命令请求,故障转移完成
1.6.4 选举新的主节点
新的主节点是通过选举产生的。
以下是集群选举新的主节点的方法:
- 集群的配置纪元是一个自增计数器,它的初始值为0
- 当集群里的某个节点开始一次故障转移操作时,集群配置纪元的值会被增1
- 对于每个配置纪元,集群里每个负责处理槽的主节点都有一次投票的机会,而第一个向主节点要求投票的从节点将获得主节点的投票
- 当从节点发现自己正在复制的主节点进入已下线状态时,从节点会向集群广播一条
CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST
消息,要求所有收到这条消息,并且具有投票权的主节点向这个从节点投票 - 如果一个主节点具有投票权,并且这个主节点尚未投票给其他从节点,那么主节点将向要求投票的从节点返回一条
CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK
消息,表示这个主节点支持从节点成为新的主节点 - 每个参与选举的从节点都会接收
CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK
消息,并根据自己收到了多少条这种消息来统计自己获得了多少主节点的支持 - 如果集群里有N个投票权的主节点,那么当一个从节点收集到大于等于
N/2+1
张支持票时,这个从节点就会当选为新的主节点 - 因为在每一个配置纪元里面,每个具有投票权的主节点只能投一次票,所以如果有N个主节点进行投票,那么具有大于等于
N/2+1
张支持票的从节点只会有一个,这确保了新的主节点只有一个 - 如果在一个配置纪元里面没有从节点收集到足够多的支持票,那么集群进入一个新的配置纪元,并再次进行选举,直到选出新的主节点为止
1.7 消息
集群中的每个节点通过发送和接收消息来进行通信,我们称发送消息的节点为发送者,接收消息的节点为接收者。
节点发送的消息主要有以下五种:
- MEET消息:当发送者接到客户端发送的CLUSTER MEET命令时,发送者会向接受者发送MEET消息,请求接收者加入到发送者当前所处的集群里面
- PING消息:集群里的每个节点默认每隔一秒钟就会从已知节点列表中随机选出五个节点,然后对这五个节点中最长时间没有发送过PING消息的节点发送PING消息,以此来检测被选中的节点是否在线。除此之外,如果节点A最后一次收到节点B发送的PONG消息的时间,距离当前时间已经超过了节点A的cluster-node-timeout选项设置时长的一半,那么节点A也会向节点B发送PING消息,这可以防止节点A因为长时间没有随机选中节点B作为PING消息的发送对象而导致对节点B的信息更新滞后。
- PONG消息:当接收者收到发送者发来的MEET消息或者PING消息时,为了向发送者确认这条MEET消息或者PING消息已到达,接收者会向发送者返回一条PONG消息。另外,一个节点也可以通过向集群广播自己的PONG消息来让集群中的其他节点立即刷新关于这个节点的认识
- FAIL消息:当一个主节点A判断主节点B已经进入FAIL状态时,节点A会向集群广播一条关于节点B的FAIL消息,所有收到这条消息的节点都会立即将结点B标记为已下线
- PUBLISH消息:当节点接收到一个PUBLISH命令时,节点会执行这个命令,并向集群广播一条PUBLISH消息,所有接收到这条PUBLISH消息的节点都会执行相同的PUBLISH命令
一条消息由消息头和消息正文组成
1.7.1 消息头
节点发送的所有消息都由一个消息头包裹,消息头除了包含消息正文之外,还记录了消息发送者自身的一些信息,因为这些信息也会被消息接受者用到。
typedef struct{
// 消息的长度(包含这个消息头的长度和消息正文的长度
uint32_t totlen;
// 消息的类型
uint16_t type;
// 消息正文包含的节点信息数量
// 只在发送MEET,PING,PONG这三种Gossip协议消息时使用
uint16_t count;
// 发送者所处的配置纪元
uint64_t currentEpoch;
// 如果发送者是一个主节点,那么这里记录的是发送者的配置纪元
// 如果发送者是一个从节点,那么这里记录的是发送者正在复制的主节点的配置纪元
uint64_t configEpoch;
// 发送者的名字(ID)
char sender[REDIS_CLUSTER_NAMELEN];
// 发送者目前的槽指派信息
unsigned char myslots[REDIS_CLUSTER_SLOTS/8];
// 如果发送者是一个从节点,这里记录的是发送者正在复制的主节点的名称
// 如果发送者是一个主节点,这里记录的是REDIS_NONE_NULL_NAME
char slaveof[REDIS_CLUSTER_NAMELEN];
// 发送者的端口号
uint16_t port;
// 发送者的标识值
uint16_t flags;
// 发送者所处集群的状态
unsigned char state;
// 消息的正文
union clusterMsgData data;
}clusterMsg;
消息正文:
union clusterMsgData{
// MEET,PING,PONG消息的正文
struct{
// MEET,PING,PONG消息都包含两个
// clusterMsgDataGossip结构
clusterMsgDataGossip gossip[1];
}ping;
// FAIL消息的正文
struct{
clusterMsgDataFail about;
}fail;
// publish西消息的正文
struct{
clusterMsgDataPublish msg;
}publish;
}
clusterMsg结构的currentEpoch,sender,myslots等属性记录了发送者自身的节点信息,接收者会根据这些信息,在自己的clusterState.nodes字典里找到发送者对应的clusterNode结构,并对结构进行更新。
1.7.2 MEET,PING,PONG消息的实现
clusterMsgDataGossip结构记录了被选中节点的名字,发送者与被选中节点最后一次发送和接受PING消息和PONG消息的时间戳,被选中节点的IP地址和端口号,以及被选中节点的标识值
typedef struct{
// 节点的名字
char nodename[REDIS_CLUSTER_NAMELEN];
// 最后一次向该节点发送PING消息的时间戳
uint32_t ping_sent;
// 最后一次从该节点接收到PONG消息的时间戳
uint32_t pong_received;
// 节点的IP地址
char ip[16];
// 节点的端口号
uint16_t port;
// 节点的标识值
uint16_t flags;
}clusterMsgDataGossip;
当接收者收到MEET,PING,PONG消息时,接收者会访问消息正文中的两个clusterMsgDataGossip结构,并根据自己是否认识clusterMsgDataGossip结构中记录的被选中节点来选择进行哪种操作:
- 如果被选中节点不存在于接收者的已知节点列表,那么说明接收者是第一次接触到被选中节点,接收者将根据结构中记录的IP地址和端口号信息,与被选中节点进行握手
- 如果被选中节点已经存在于接收者的已知节点列表,那么说明接收者之前已经与被选择中节点进行过接触,接收者将根据clusterMsgDataGossip结构记录的信息,对被选中节点所对应的clusterNode结构进行更新。
1.7.3 FAIL消息的实现
FAIL消息的正文结构:
typedef struct{
char nodename[REDIS_CLUSTER_NAMELEN];
}clusterMsgDataFail;
因为集群里的所有节点都有一个独一无二的名字,所以FAIL消息里面只需要保存下线节点的名字,接收到消息的节点可以根据这个名字来判断是那个节点下线了
1.7.4 PUBLISH消息的实现
当客户端向集群中的某个节点发送命令:publish <channel> <message>
的时候,接收到PUBLISH命令的节点不仅会向channel频道发送消息message,它还会向集群广播一条publish消息,所有接收到这条publish消息的节点都会向channel频道发送message消息。
publish消息正文:
typedef struct{
uint32_t channel_len;
uint32_t message_len;
// 定义为8字节只是为了对其其他消息结构
// 实际的长度由保存的内容决定
unsigned char bulk_data[8];
}clusterMsgDataPublish;
bulk_data属性是一个字节数组,这个字节数组保存了客户端的通过publish们命令发送给节点的channel参数和message参数,而结构的channel_len和message_len则分别保存了channel参数的长度和message参数的长度
- 其中bulk_data的0字节至channel_len-1字节保存的是channel参数
- 而bulk_data的channel_len字节至channel_len + message_len - 1字节保存的则是message参数
其他REDIS文章:
REDIS(一) 数据结构与对象
REDIS(二) REDIS持久化-RDB持久化
REDIS(三) REDIS持久化-AOF持久化
REDIS(四) 主从复制的实现
REDIS(五) 过期键的删除策略
REDIS(六) 哨兵模式的实现