《Redis设计与实现》—— Redis底层原理与实现(下)

多机数据库的实现

十四、复制

​ 用户可以通过执行SLAVEOF命令以及设置slaveof选项,让一个服务器去复制另一个服务器。

在这里插入图片描述

14.1 旧版复制功能

Redis的复制功能分为同步与命令传播两个操作;

1、同步

客户端向从服务器发送SLAVEOF命令,则从服务器需要先执行同步操作,,从服务器对主服务器的同步操作需要通过向主服务器发送SYNC命令来完成,以下为SYNC的执行步骤:

在这里插入图片描述

1、从向主发送SYNC命令;
2、主收到后执行BGSAVE命令,后台生成一个RDB文件,使用缓冲区记录从现在开始执行的所有写命令;
3、BGSAVE执行完,主发送RDB文件给从,从接收并载入RDB;
4、主发送缓冲区保存的所有写命令,藏执行这些写命令;

2、命令传播

执行完同步后,客户端对主服务器的所有后续操作,主服务器都会向从发送执行的命令来保证后续的一致性;

14.2 旧版复制缺陷

复制分为初次复制和断线后重复制,对于初次复制,旧版实现可以很好完成,但对于断线后重复制,会出现下面的例子:

在这里插入图片描述

问题即是为了让从服务器不足一部分缺失的数据,却得让主从服务器重新执行一次SYNC命令,这样的效率分成低;

14.3 新版复制功能

新版使用PSYNC命令替换SYNC命令,而PSYNC命令具有完整同步和部分同步两种模式;部分同步就解决了旧版的低效问题;

在这里插入图片描述

1、部分重同步实现

  • 主服务器的复制偏移量和从服务器的复制偏移量;
  • 主服务器的复制积压缓冲区;
  • 服务器运行的ID;

复制偏移量

主从都分别维护一个复制偏移量;主每次发送N个字节数据,自己的复制偏移量就+N;从收到主的N个字节数据,自己的复制偏移量也+N。通过复制偏移量可以知道主从的一致性;

复制积压缓冲区

由主服务器维护的一个固定长度先进先出队列,默认为1MB(固定长度先进先出队列执行时,当入队元素数量>队列长度时,最先入队元素会被弹出,新元素放入);
主进行命令传播时,不仅发送命令给从,还会将写命令入队到复制积压缓冲区。
当从重新连上主时,从会通过PSYNC命令将自己的复制偏移量offset发给主,主根据offset来决定执行何种同步:
若offset偏移后的数据存在于复制积压缓冲区,则进行部分同步;不在则进行完整重同步;

在这里插入图片描述

服务器运行ID

每个Redis服务器都有自己的运行ID,从对主进行初次复制时,主会发送自己的ID给从,从会保存下来,之后断线重连时,从会发送保存的ID给主,若ID相同,则主可以尝试执行部分重同步,不同则需要执行完整重同步;

14.4 PSYNC命令实现

在这里插入图片描述

14.5 复制实现

通过命令SLAVEOF <master_ip> <master_port>;

1、步骤1:设置主服务器的地址和端口

struct redisServer {
    //....
    //主服务器地址
    char *masterhost;
    //主服务器端口
    int masterport;
    //...
};

2、步骤2:建立套接字

3、步骤3:发送PING命令

在这里插入图片描述

4、步骤4:身份验证

在这里插入图片描述

5、步骤5:发送端口信息

步骤四之后,从将执行REPLCONF listening-port ,向主发送从的监听端口号。主会将端口号记录在从对应的客户端状态的slave_listening_port属性中:

typedef struct redisClient {
    //...
    //从的监听端口号
    int slave_listening_port;
    //...
}redisClient;

6、步骤6:同步

7、步骤7:命令传播

14.6 心跳检测

命令传播阶段,从会默认每秒一次向主发送命令:REPLCONF ACK <replication_offset>,其中replication_offset就是从服务器当前的复制偏移量;

该命令作用:

1、检测主从服务器的网络连接状态

若主服务器发送INFO replication命令,在列出的从服务器列表的lag一栏中,可看到相应从最后一次向主发送REPLCONF ACK 命令距离现在过了多少秒:(lag一般在0或1之间跳动,超过1,说明主从连接出现故障)

127.0.0.1:6379> INFO replication
role:master
....
slave0:ip=.....,lag=0 # 刚刚发送过REPLCONF ACK 命令
slave1:ip=.....,lag=15 # 15秒前发送过REPLCONF ACK 命令

2、辅助实现min-slaves配置选项

Redis的min-slaves-to-write和min-slaves-max-lag可放置主服务器在不安全情况下执行写命令;

举个例子:

min-slaves-to-write 3
min-slaves-max-lag 10
# 从服务器数量<3个或三个从服务器的延迟(lag)都>=10s,则主服务器拒绝执行写命令

3、检测命令丢失

若由于网络故障,主给从发送的命令丢失,则从给主发送REPLCONF ACK 时,主会发现replication_offset不一致,所以会去找复制积压缓冲区里对应的指令进行命令的补发

注意:补发缺失数据操作在主从没有断线情况下执行;而部分重同步时主从断线并重连后执行。

十五、Sentinel

​ 由一个或多个Sentinel(哨兵、哨岗)组成的Sentinel系统可监视多台主从服务器,主服务器下线时,可进行故障转移,将从服务器升级为主服务器。

在这里插入图片描述

在这里插入图片描述

15.1 启动并初始化Sentinel

# 启动一个Sentinel可以使用的命令
$ redis-sentinel /path..../sentinel.conf
或 $ redis-server /path.../sentinel.conf --sentinel

启动一个Sentinel时执行的步骤:

1、初始化服务器

Sentinel本质是一个特殊模式下的Redis服务器,所以初始化步骤与13章类似。不过略有不同,主要功能使用情况如下:

在这里插入图片描述

2、使用Sentinel专用代码

例如使用sentinel.c/sentinelcmds作为服务器的命令表,使用sentinel.c/REDIS_SENTINEL_PORT常量值作为服务器端口;

3、初始化Sentinel状态

保存服务器中所有和Sentinel功能相关的状态:

struct sentinelState {
    //当前纪元,用于实现故障转移
    uint64_t current_epoch;
    
    //保存所有被这个sentinel监视的主服务器
    //字典的键为主服务器名,值为指向sentinelRedisInstance结构的指针
    dict *masters;
    
    //是否进入TILT模式
    int tilt;
    
    //目前正在执行的脚本数量
    int running_scripts;
    
    //进入TILT模式的时间
    mstime_t titl_start_time;
    
    //最后一次执行时间处理器的时间
    mstime_t previous_time;
    
    //一个FIFO队列,包含所有需要执行的用户脚本
    list *scipts_queue;
} sentinel;

4、初始化Sentinel状态的masters属性

masters字典的值sentinelRedisInstance可以是主从服务器或者另一个Sentinel实例。

typedef struct sentinelRedisInstance {
	//标志值,记录实例类型以及实例当前状态
	int flags;
	
	//实例名
	//主服务器名由用户在配置文件中设置
	//从服务器以及Sentinel名由Sentinel自动设置
	//格式:ip:port
	char *name;
	
	//实例运行ID
	char *runid;
	
	//配置纪元,实现故障转移
	uint64_t config_epoch;
	
	//实例的地址
	sentinelAddr *addr;
	
	//SENTINEL down-after-milliseconds选项设定的值
	//实例无响应多少ms之后才被判断为主观下线
	mstime_t down_after_period;
	
	//SENTINEL monitor <master-name> <IP> <port> <quorum> 选项中的quorum参数
	//判断这个实例为客观下线所需的支持投票数量
	int quorum;
	
	//SENTINEL parallel-syncs <master-name> <number> 选项的值
    //在执行故障转移操作时,可以同时对新的主服务器进行同步的从服务器数量
    int parallel_syncs;
    
    //SENTINEL failover-timeout <master-name> <ms>选项的值
    //刷新故障迁移状态的最大时限
    mstime_t failover_timeout;
    
    //..
} sentinelRedisInstance;

typedef struct sentinelAddr {
    char *ip;
    int port;
} sentinelAddr;

举个例子:用户启动Sentinel时指定了包含以下内容的配置文件:

# master1 configure
SENTINEL monitor master1 127.0.0.1 6379 2
SENTINEL down-after-milliseconds master1 30000
SENTINEL parallel-syncs master1 1
SENTINEL failover-timeout master1 900000
# master2 configure
SENTINEL monitor master2 127.0.0.1 12345 5
SENTINEL down-after-milliseconds master2 30000
SENTINEL parallel-syncs master2 5
SENTINEL failover-timeout master2 900000

则Sentinel为主服务器master1和master2生成的实例结构如下:并保存到Sentinel状态的masters字典中

在这里插入图片描述

在这里插入图片描述

5、创建连向主服务器的网络连接

一共创建两个连接:命令连接和订阅连接

在这里插入图片描述

15.2 获取主服务器信息

Sentinel默认会每10s一次通过命令连接向被监视的主服务器发送INFO命令,并通过分析INFO命令的回复获取主服务器的当前信息。包括主服务的ID,角色,主服务器下的所有从服务器ip,por等;

对于新产生的或者改变的从服务器实例结构,更新保存到主服务器实例结构的slaves字典中,如图:

在这里插入图片描述

15.3 获取从服务器信息

同样是会建立命令连接和订阅连接,并通过10s一次命令连接发送INFO命令获取从服务器信息;

在这里插入图片描述

15.4 向主从服务器发送信息

默认情况下,Sentinel会每两秒一次通过命令连接向所有被监视的主从服务器发送以下命令:

# 向服务器的_sentinel_:hello频道发送消息
PUBLISH_sentinel_:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>"

以s_开头的记录的是Sentinel本身信息,各参数意义:

在这里插入图片描述

以m_开头的记录的是主服务器信息(若监视的是主服务器,则参数记录的就是主服务器信息;若监视的是从服务器,则参数记录的就是从服务器所复制的主服务器信息)

在这里插入图片描述

15.5 接收来自主从服务器的频道信息

每个与Sentinel连接的服务器,Sentinel既通过命令连接向服务器的_sentinel_:hello频道发送发送消息,又通过订阅连接服务器的_sentinel_:hello频道接收信息;

在这里插入图片描述

同时对于监视同一个服务器的多个Sentinel,一个Sentinel发送的消息会被其他Sentinel接收到,用于更新其他Sentinel的认知;

在这里插入图片描述

当一个Sentinel从_sentinel_:hello频道收到一条消息时,Sentinel会分析该消息,提取参数,并检查:

  • 若消息中的Sentinel运行ID和接收信息的Sentinel运行ID相同,说明是自己发送的,不做处理,丢弃;
  • 若不同,则接收各参数,对相应主服务器的实例结构进行更新;

1、更新sentinels字典

即是发现有新的Sentinel实例,则为新Sentinel创建一个新的实例结构sentinelRedisInstance并添加到sentinels字典里面;

2、创建连向其他Sentinel的命令连接

在这里插入图片描述

15.6 检测主观下线状态

Sentinel默认会每秒一次向与它创建了命令连接的主、从服务器、其他Sentinel发送PING命令,通过回复来判断实例在线情况;配置文件中的down-after-milliseconds指定了实例主观下线所需的时间长度。

15.7 检查客观下线状态

当Sentinel将一个主服务器定为主观下线后,回去询问其他Sentinel,看看他们是否也认为该服务器也进入下线状态,当从其他Sentinel那里收到足够数量的已下线判断后,边判定为客观下线,开始进行故障转移。

1、发送SENTINEL is-master-down-by-addr命令

使用SENTINEL is-master-down-by-addr <current_epoch> 命令询问其他Sentinel是否同意主服务器已下线;

2、接收SENTINEL is-master-down-by-addr命令

对于另一个Sentinel发出的命令,目标Sentinel会检查命令中的参数,并返回一条包含三个参数的Multi Bulk回复:

在这里插入图片描述

3、接收SENTINEL is-master-down-by-addr命令的回复

根据其他Sentinel的回复,统计同意数量,若超过Sentinel配置中设置的quorum参数的值,那么可认为主服务器已进入客观下线状态,此时会打开主服务器实例结构flags属性的SRI_O_DOWN标识,表示已客观下线。

在这里插入图片描述

15.8 选举领头Sentinel

当一个主服务器客观下线,监视该主服务器的各个Sentinel协商选举中一个领头Sentinel,并由领头Sentinel执行故障转移,选举规则如下:

在这里插入图片描述

15.9 故障转移

故障转移步骤:

1、选出新的主服务器

从所有已下线主服务器属下的所有从服务器中挑选出一个状态良好,数据完整的从服务器,然后向这个从服务器发送SLAVEOF no one命令,将该服务器转为主服务器。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2、修改从服务器的复制目标

在这里插入图片描述

在这里插入图片描述

3、将就主服务器变为从服务器

在这里插入图片描述

十六、集群

​ Redis集群是Redis提供的分布式数据库方案,集群通过分片来进行数据共享,并提供复制和故障转移功能。

16.1 节点

每台Redis服务器即是一个节点,一个集群可由多个节点组成,连接各个节点的命令:

# 该命令会向指定节点进行握手,若成功,则会将添加节点进入当前所在集群
CLUSTER MEET <ip> <port>

# 查看当前集群节点
CLUSTER NODES

接下来介绍启动节点的方法、与集群相关的数据结构以及CLUSTER MEET 命令实现原理:

1、启动节点

Redis启动时会根据cluster-enabled配置是否为yes来决定是否开启服务器的集群模式

在这里插入图片描述

节点会继续使用所有在单机模式中使用的服务器组件,比如使用数据库来保存键值对数据等等。

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;
    
    //...
}

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;
    
    //集群中至少处理者一个槽的节点的数量(16.2节介绍)
    int size;
    
    //集群节点名单,字典键位节点名,值位节点对应的clusterNode
    dict *nodes;
    
    //...
} clusterState;

举个例子:现有三个Redis服务器,其中一台服务器(以7000端口开启)分别采用CLUSTER MEET连接其他两台服务器形成一个集群,则对应的clusterState结构如下所示:

在这里插入图片描述

3、CLUSTER MEET命令实现

下面以节点A向B进行握手过程解析:

  • A为B创建一个clusterNode结构,添加到自己的clusterState.node中;
  • A根据IP地址和PORT向B发送一条MEET消息;
  • B收到MEET消息,同样会为A创建一个clusterNode结构,添加到自己的clusterState.node中;
  • B向A返回一条PONG;
  • A收到PONG,返回一条PING;
  • B接收到PING,握手完成;

在这里插入图片描述

之后,A会通过Gossip协议将B的信息传播到其他节点,让其他节点也与B进行握手;

16.2 槽指派

Redis集群通过分片来保存数据库中的键值对,整个数据库被分为16384个槽(slot),每个键都属于这些槽的其中一个。当所有槽都有节点在处理,集群上线(ok),否则任意一个槽没被处理,集群均处于下线(fail)。

通过命令CLUSTER ADDSLOTS来分派槽:

127.0.0.1:7000> CLUSTER ADDSLOTS 0 ... 5000
OK

# 可以通过CLUSTER INFO 查看集群状态
127.0.0.1:7000> CLUSTER INFO

1、记录节点的槽指派信息

struct clusterNode {
    //...
    
    //16384/8=2048个字节,包含16384个二进制位,根据索引i上的二进制位值来判断节点是否负责处理槽i(1代表处理,0则不处理)
    unsigned char slots[16384/8];
    
    int numslots;
    
    //
};

举个例子:

在这里插入图片描述

2、传播节点的槽指派信息

一个节点会将自己的槽指派信息slots数组和numslots发送给其他节点,其他节点接收到后保存至相应节点的clusterNode结构中。

3、记录集群所有槽的指派信息

typedef struct clusterState {
	//....
	//若slots[i]为NULL,那么表示槽i尚未指派任何节点
	clusterNode *slots[16384];

	//...
}

在这里插入图片描述

4、CLUSTER ADDSLOTS命令的实现

该命令接收一个或多个槽作为参数,并将所有输入的槽指派给接收该命令的节点负责,及修改clusterState以及clusterNode结构中对应的slots数据信息。

16.3 在集群中执行命令

在这里插入图片描述

例如:

127.0.0.1:7000> set msg "happy new year!"
-> Redirected to slot [6257] located at 127.0.0.1:7001
OK

127.0.0.1:7000> GET msg
"happy new year!"

1、计算键的槽位

def slot_number(key):
	return CRC16(key) & 16383

# 查看key所在槽位
CLUSTER KEYSLOT "msg"

2、MOVED错误

当节点发现键所在的槽不是由自己处理时,节点会向客户端返回一个MOVED错误,指引客户端转向正在负责槽的节点;

# 客户端根据ip:port转向负责处理槽的节点,并由该节点执行命令
MOVED <slot> <ip>:<port>

16.4 重新分片

​ 重分片可以将任意数量已指派给某个节点的槽改为指派给另一个节点,且相关槽所属键值对也会从源节点被移动到目标节点,此过程中,集群不需要下线,且源/目标节点可继续处理命令。

Redis集群管理软件redis-trib负责执行重新分片,步骤如下:

在这里插入图片描述

迁移键的过程:

在这里插入图片描述

16.5 ASK错误

在这里插入图片描述

1、CLUSTER SETSLOT IMPORTING命令的实现

typedef struct clusterState {
    //...
    //记录当前节点正在从其他节点导入的槽
    clusterNode *importing_slots_from[16384];
    
    //...
}clusterState;

命令格式:

# 可实现将对应的槽指针指向source_id对应的节点
CLUSTER SETSLOT <i> IMPORTING <source_id>

2、CLUSTER SETSLOT MIGRATING命令的实现

typedef struct clusterState {
    //...
    //记录当前节点正在迁移至其他节点的槽
    clusterNode *migrating_slots_to[16384];
    
    //...
}clusterState;

命令格式:

# 可实现将对应的槽指针指向target_id对应的节点
CLUSTER SETSLOT <i> MIGRATING <target_id>

3、ASK错误

若节点收到的关于键的命令请求,在自己数据库中找不到键,则会检查自己的migrating_slots_to[i],看键是否正在迁移,若正在迁移,则会向客户端发送一个ASK错误,接到ASK错误的客户端会根据错误提供的IP和PORT转向目标节点,发送一个ASKING命令,之后再重新发送原本要执行的命令;

4、ASKING命令

客户端接收到ASKING命令,会打开发送该命令的客户端的REDIS_ASKING标识。

在这里插入图片描述

16.6 复制与故障转移

本节将介绍节点的复制方法,检测节点是否下线的方法以及对下线主节点进行故障转移的方法。

1、设置从节点

向一个节点发送命令:

# 让接收命令的节点成为node_id对应节点的从节点,并开始对主节点进行复制,并修改自己在clusterState.myself.flags中的属性
CLUSTER REPLICATE <node_id>
struct clusterNode {
    //...
    
    //如果为从节点,那么指向主节点
    struct clusterNode *slaveof;
}

同时会通过消息发送给集群中的其他系欸但,告知情况,其他节点会进行保存,方式如下:

struct clusterNode {
    //...
    
    //正在复制这个主节点的从节点数量
    int numslaves;
    
    //每个数组项指向一个正在复制这个主节点的从节点的clusterNode结构
    struct clusterNode **slaves;
}

2、故障检测

集群中每个节点定期向其他节点发送PING消息,如果接收方没有在规定时间内返回PONG消息,则被发送PING消息节点标记为疑似下线,之后各节点通过互相发送消息得知集群中各个节点的状态信息。例如A通过消息得知B认为C进入疑似下线状态,A会在自己的clusterState.nodes字典中找到C对应的clusterNode结构,并将B的下线报告添加到clusterNode结构的fail_reports中:

struct clusterNode {
    //...
    // 记录所有其他节点对该节点的下线报告
    list *fail_reports;
    
    //...
};

每个下线报告结构如下:

struct clusterNodeFailReport {
    //报告目标节点已下线的节点
    struct clusterNode *node;
    
    //最后一次从node节点收到下线报告的时间
    mstime_t time;
}typedef clusterNodeFailReport; 

若集群中半数以上负责处理槽的主节点都将某个主节点x报告为疑似下线,那么这个主节点x将被标记为已下线(Fail),将x标记为已下线的节点会广播x下线的消息,所有收到这条消息的节点会将x标记为已下线。

3、故障转移

从节点开始对下线主节点进行故障转移,步骤为:

  • 复制下线主节点的所有从节点,会有一个被选中,选中的节点执行slaveof no one命令成为主节点;
  • 新主节点撤销已下线主节点的槽指派,将这些槽都指派给自己;
  • 新主节点向集群广播一条PONG告知情况;
  • 新主节点开始接收和处理自己负责的槽有关的命令请求;

4、选举新的主节点

与Sentinel选举头领方法非常相似,在此不再赘述。

16.7 消息

节点发送的消息分为:

MEET消息、PING消息、PONG消息、FAIL消息、PUBLISH消息

一条消息分为消息头和正文。

1、消息头

每个消息头都由一个cluster.h/clusterMsg结构表示:

typedef struct {
    //消息长度(头+正文)
    uint32_t totlen;
    
    //消息类型
    uint16_t type;
    
    //消息正文包含的节点信息数量(发送MEET、PING和PONG时才需要)
    uint16_t count;
    
    //发送者所处的配置纪元
    uint64_t currentEpoch;
    
    //若发送者是主节点,则记录的是发送者配置纪元
    //若发送者是从节点,则记录的是复制的主节点配置纪元
    uint64_t configEpoch;
    
    //发送者名字(ID)
    char sender[REDIS_CLUSTER_NAMELEN];
    
    //发送者目前所在的槽指派信息
    unsigned char myslots[REDIS_CLUSTER_SLOTS/8];
    
    //若发送者是主节点,则记录的是REDIS_NODE_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[1];
    } ping;
    
    //FAIL消息正文
    struct {
         clusterMsgDataFail about;
    }fail;
    
    //PUBLISH消息正文
    struct {
        clusterMsgDataPublish msg;
    }publish;
    
    //其他消息的正文
};

2、MEET、PING、PONG消息实现

//发送者每次发送时会从自己已知节点列表中选取两个节点记录到clusterMsgDataGossip中
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;

接收者收到消息后比对clusterMsgDataGossip中的信息,若其中的节点存在于接收节点已知列表,则进行节点信息更新,否则与被选中节点进行握手。

3、FAIL消息实现

typedef struct {
	char nodename[REDIS_CLUSTER_NAMELEN];
}clusterMsgDataFail;

集群中每个节点的名称都是唯一的,所以FAIL消息只需保存下线节点的名字即可。

在这里插入图片描述

4、PUBLISH消息实现

客户端向集群中某个节点发送:

PUBLISH <channel> <message>

接收到该命令的节点会向channel频道发送消息message,并广播其他集群节点也向该channel发送message。

typedef struct {
	//保存channel参数的长度
	uint32_t channel_len;
	
	//保存message参数的长度
	uint32_t message_len;
	
	//长度由保存的内容来定,channel_len+message_len-1;
	bulk_data;
}clusterMsgDataPublish;
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页