-
Notifications
You must be signed in to change notification settings - Fork 2
Redis 集群
Redis集群是Redis提供的分布式数据库方案,集群通过分片来进行数据共享,并提供复制和故障转移功能。
使用cluster meet命令将节点加入到集群节点中,
命令实现:假如节点A收到客户端发送的CLUSTER MEET ,IP和port为节点B
- 节点A会为节点B创建一个clusterNode结构,并将该节点添加到自己的clusterState.nodes字典中。
- 节点A将根据指定的IP和port,发送一条MEET消息。
- 节点B收到节点A的MEET消息后,创建一个clusterNode结构,并将该节点添加到自己的clusterState.nodes字典中。
- 节点B向节点A返回一条PONG消息。
- 节点A收到PONG消息后,将向节点B发送PING消息。
- 节点B收到PING消息后,表示握手完成。
集群的整个数据库被分成16384个槽,数据库中的每个键都属于这16384个槽中的一个,集群中的每个节点可以处理0个或16384个槽。 当集群中的16384个槽都有节点在处理时,集群处于上线,否则处于下线。
通过向节点发送CLUSTER ADDSLOTS命令,将一个或多个槽指派给其负责。
typedef struct clusterNode {
......
// 将数据库分成16384个二进制位,位上的值如果为1表示,该节点处理该槽位,否则不处理。
unsigned char slots[CLUSTER_SLOTS/8]; /* slots handled by this node */
// 节点负责处理槽的个数
int numslots; /* Number of slots handled by this node */
......
} clusterNode;
传播节点的槽指派信息 节点除了保存节点自己的处理槽信息外,会通过消息,发送给集群中的其他节点告知节点处理槽,当集群的其他节点收到后会在clusterState.nodes字典中找到对应的节点,更新其处理槽信息
记录集群中所有槽指派信息
typedef struct clusterState {
.....
// 记录集群中所有槽的指派信息
clusterNode *slots[CLUSTER_SLOTS];
......
} clusterState;
伪代码:
节点执行:cluster addslots 1 2
当集群所有槽都被指派后,集群将上线,这时如果向集群发送命令后,集群节点会计算该槽位是否指派给自己。
如果该节点正是指派给当前节点的,则直接处理命令。
否则,节点将向客户端返回一个MOVED错误,指引客户端转向正确的节点,并重新发送之前要执行的命令。
MOVED格式: MOVED
-
客户端发送命令返回MOVED错误。
-
客户端根据MOVED,重新转向服务器,并重新发送命令
将某些属于原来节点(源节点)的槽重新指派给其他节点(目标节点)
实现步骤 假设将属于节点7002的槽重新指派给7003节点。
- redis-trib对目标节点发送CLUSTER SETSLOT IMPORTING <source_id>命令,让目标节点准备好从源节点导入(import)属于槽slot的键值对。
typedef struct clusterState {
.....
// 记录当前节点正在从其他节点导入的槽
clusterNode *importing_slots_from[CLUSTER_SLOTS];
......
} clusterState;
执行上面命令后,会将目标节点(7003)中的importing_slots_from对应槽位(solt)指向源节点(7002)。
- redis-trib对源节点发送CLUSTER SETSLOT MIGRATING <target_id>命令,让源节点准备好将属于槽slot的键值对迁移(migrate)到目标节点。
typedef struct clusterState {
......
// 记录将槽迁移至指定节点
clusterNode *migrating_slots_to[CLUSTER_SLOTS];
......
}
将源节点(7002)属性migrating_slots_to的槽(solt)设置为要迁移的目标节点(7003)
- redis-trib向源节点发送CLUSTER GETKEYSINSLOT 命令,获得最多count个属于槽slot的键值对的键名。
- 对于步骤3中的每个键名,redis-trib都向源节点发送一个MIGRATE <target_id> <target_port> <key_name> 0 命令, 将被选中的键原子的从源节点迁移到目标节点
- 重复执行3、4步骤,直到所有键值对都被迁移完成。
- redis-trib向集群中的任意一个节点发送CLUSTER SETSLOT NODE <target_id>命令,告诉其他节点slot槽已经指派给了目标节点。
迁移过程:
对槽slot进行重新分片的过程:
当客户端向源节点发送一个与数据库键有关的命令,此时该键正处于迁移过程中时。
-
首先,客户端发送命令,到源节点执行命令,如果源节点中不存在键,则去正在执行迁移的槽中找,如果存在则返回一个ASK错误
-
根据上一步的返回结果,重新定位,并执行命令
过程:
MOVED:代表槽的负责权已经从一个节点转移到另外一个节点中。 ASK:代表槽节点正在迁移。
设置从节点
-
客户端发送命令,CLUSTER REPLICATE <node_id>,让接收命令的节点成为node_id的从节点
-
接收到命令的节点,首先在clusterState.nodes字典中找到node_id所对应的节点的clusterNode结构(主节点),并将自己节点的slaveof指向主节点
typedef struct clusterState {
clusterNode *myself; /* This node */
// ......
} clusterState;
typedef struct clusterNode {
// ......
// 如果这是一个从节点,则指向他的主节点
struct clusterNode *slaveof;
// ......
} clusterNode;
-
节点修改自己的flag标志,打开REDIS_NODE_SLAVE标识。
-
节点调用复制代码,根据slaveof指向的IP和port,对主节点进行复制。
-
同时还会将成为某个节点的从节点,并复制数据这个情况通过消息发送给集群中的其他节点。并更新其节点的从节点信息
typedef struct clusterNode {
// ......
// 正在复制这个主节点的从节点个数
int numslaves; /* Number of slave nodes, if this is a master */
// 一个数组,每个数组元素指向复制这个主节点的从节点对象
struct clusterNode **slaves; /* pointers to slave nodes */
// ......
} clusterNode;
故障检测 集群中的每个节点都会定期向集群中的其他节点发送PING命令,以此检测节点是否在线,如果接收到节点未在规定时间内返回PONG消息,那么发送PING消息的节点会将接收PING消息的节点标记为疑似下线,即打开REDIS_NODE_PFAIL标识。
当一个主节点A(7001),通过消息从主节点B(7002)中得知主节点C(7000)疑似下线,主节点A会在clusterState.nodes字典中找到主节点C所对应的clusterNode结构, 并将主节点B的下线报告添加到clusterNode结构的fail_reports中。
typedef struct clusterNodeFailReport {
// 报告目标节点已经下线的节点
struct clusterNode *node; /* Node reporting the failure condition. */
// 最后一次从node节点收到下线的时间
mstime_t time; /* Time of the last report from this node. */
} clusterNodeFailReport;
typedef struct clusterNode {
// ......
// 记录其他节点对该节点的下线报告(clusterNodeFailReport)
list *fail_reports; /* List of nodes signaling this as failing */
} clusterNode;
主节点中C节点结构:
当超过半数的主节点认为某个主节点x疑似下线,则会将主节点x标记为已下线,并向集群广播主节点x的FAIL消息。
故障转移 当一个从节点发现其主节点已经下线,从节点将开始对主节点进行故障转移:
-
复制下线主节点的所有从节点里面,会有一个从节点被选中。
-
被选中的节点会指向slaveof no one命令,成为新主节点。
-
新的主节点会撤销所有对以下线主节点的槽指派,并将这些槽指派信息指向新主节点。
-
新的主节点向集群中广播一条PONG消息,告知集群其他主节点,其成为新的主节点,并更新其相关信息
-
新主节点处理槽相关命令请求,完成迁移。
选举新的主节点 一些选举规则: 集群的配置纪元是一个自增计数器,初始值为0 当集群中的某个节点开始一次故障转移时,配置纪元都会自增1, 对于每个配置纪元,集群中每个负责处理槽的主节点都有一次投票机会,而第一个向主节点要求投票的从节点将获取主节点的投票。 在每一个配置纪元,每个具有投票权限的节点,只能投一次票。 如果在一个配置纪元里面,还未选出主节点,那么集群将进入一个新的配置纪元,直至选出新的主节点。
步骤:
-
从节点发现自己的主节点下线,从节点会向集群主节点广播一条CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息,要求收到消息、具有投票权的主节点向这个从节点投票。
-
如果主节点具有投票权,且未向其他从节点投过票,主节点将向从节点返回一条CLUSTERMSG_TYPE_FAILOVER_AUTH_OK消息,表示这个从节点支持从节点成为新的主节点。
-
每个参与选举的从节点都会接收CLUSTERMSG_TYPE_FAILOVER_AUTH_OK消息,并根据接收到多少条这样的消息统计有多少个主节点支持它。
-
如果集群中有N个节点具有投票权限,那么当一个节点获得的票数大于等于N/2 + 1,这个从节点将成为新的主节点
MEET | PING | PONG | FAIL | PUBLISH |
---|---|---|---|---|
接收者加入到发送者所在的集群中 | 集群每个节点默认每1秒会从已知节点列表中随机选出5个节点,然后对着5个节点中最长时间没发送过PING消息的节点发送PING消息,以此检测节点是否在线。 | 告知节点已经收到MEET或PING消息、向集群广播自己的PONG消息,让其他节点刷新节点 | 节点广播一条节点下线的FAIL消息,收到消息的节点将会标记下线节点 | 节点收到一个PUBLISH命令时,节点执行命令,并向集群广播一头PUBLISH消息 |
消息 消息头
typedef struct {
char sig[4]; /* Signature "RCmb" (Redis Cluster message bus). */
// 消息长度,包括头和体
uint32_t totlen; /* Total length of this message */
uint16_t ver; /* Protocol version, currently set to 1. */
// 发送者端口
uint16_t port; /* TCP base port number. */
// 消息类型
uint16_t type; /* Message type */
// 消息发送消息体包含的信息数量,只在MEET、PING、PONG三种消息时使用
uint16_t count; /* Only used for some kind of messages. */
// 发送者所处的配置纪元
uint64_t currentEpoch; /* The epoch accordingly to the sending node. */
// 如果发送则是主节点,则记录其配置纪元,如果是从节点,则是其复制的主节点的配置纪元
uint64_t configEpoch; /* The config epoch if it's a master, or the last
epoch advertised by its master if it is a
slave. */
uint64_t offset; /* Master replication offset if node is a master or
processed replication offset if node is a slave. */
// 发送者名字
char sender[CLUSTER_NAMELEN]; /* Name of the sender node */
unsigned char myslots[CLUSTER_SLOTS/8];
// 如果发送者是一个从节点,则记录其复制主节点的名字,如果是主节点则记录REDIS_NODE_NULL_NAME
char slaveof[CLUSTER_NAMELEN];
char myip[NET_IP_STR_LEN]; /* Sender IP, if not all zeroed. */
char notused1[34]; /* 34 bytes reserved for future usage. */
uint16_t cport; /* Sender TCP cluster bus port */
// 发送者标识
uint16_t flags; /* Sender node flags */
// 发送者所处集群状态
unsigned char state; /* Cluster state from the POV of the sender */
unsigned char mflags[3]; /* Message flags: CLUSTERMSG_FLAG[012]_... */
// 消息体
union clusterMsgData data;
} clusterMsg;
消息体
- MEET、PING、PONG三种消息
typedef struct {
// 节点的名字
char nodename[CLUSTER_NAMELEN];
// 最后一次向这个节点发送Ping的时间戳
uint32_t ping_sent;
// 最后一次从这个节点接收到PONG消息的时间戳
uint32_t pong_received;
// 节点IP地址
char ip[NET_IP_STR_LEN]; /* IP address last time it was seen */
// 节点的端口号
uint16_t port; /* base port last time it was seen */
uint16_t cport; /* cluster port last time it was seen */
// 节点的标识符
uint16_t flags; /* node->flags copy */
uint32_t notused1;
} clusterMsgDataGossip;
每次发送MEET、PING、PONG消息时,发送者都是从自己的已知列表中随机选择两个节点,并将这两个节点保存到两个clusterMsgDataGossip结构中。
当接收者接收到MEET、PING、PONG消息时,根据消息体中两个clusterMsgDataGossip结构:
1)如果被选中的节点不存在与接收者已知队列中,提取IP和端口进行节点握手
2)如果已经在已知列表中,则做更新节点操作
- FAIL消息 当集群中的某个节点A将节点B标识为已下线时,主节点A将向集群广播一条关于节点B的FAIL消息。
typedef struct {
// 节点的名字
char nodename[CLUSTER_NAMELEN];
} clusterMsgDataFail;
节点接收和发送(FAIL)消息的过程
- PUBLISH消息 当客户端向集群中的一个节点发送PUBLISH 命令时,将导致集群中的所有节点都向channel频道发送message消息。 9db293c4a254a7c6ecff7d3f929baba0.png
typedef struct {
// publish <channel> <message>
uint32_t channel_len;
uint32_t message_len;
// 数组的0 - channel_len-1 保存channel信息
// channel_len - channel_len+message_len-1字节保存message信息
unsigned char bulk_data[8]; /* 8 bytes just as placeholder. */
} clusterMsgDataPublish;
例如:
publish "news.it" "hello"