redis集群原理
本文部分图片和文字来源于:https://www.jianshu.com/p/0232236688c1
摘要
redis集群是redis官方提供的分布式缓存方案,通过分片实现数据的集群化,对于redis集群我们需要关注这样几个方面,节点连接、槽指派、命令执行过程、重新分片、转向、故障检测、故障转移、消息通信等。在学习完之后至少需要知道这样几个问题:
- 一个集群是如何将一台redis服务器加入到自己的集群的?
- 集群使用了什么样的数据结构保存节点之间的状态信息?
- 槽指派的过程是怎样的?
- 来自客户端的命令请求是怎样被集群处理的?
- moved错误和ASK错误是什么?有什么区别
- redis集群某个结点发生故障如何进行故障转移
- 如何进行故障检测?
- 主服务器挂掉后新的主节点的选举过程是怎样的?
- 集群中的消息通信手段(命令)
1. 一个集群是如何将一台redis服务器加入到自己的集群的?


meet命令: cluster meet ip port
CLUSTER MEET命令实现:
1)节点A会为节点B创建一个clusterNode结构,并将该结构添加到自己的clusterState.nodes字典里面。
2)节点A根据CLUSTER MEET命令给定的IP地址和端口号,向节点B发送一条MEET消息。
3)节点B接收到节点A发送的MEET消息,节点B会为节点A创建一个clusterNode结构,并将该结构添加到自己的clusterState.nodes字典里面。
4)节点B向节点A返回一条PONG消息。
5)节点A将受到节点B返回的PONG消息,通过这条PONG消息节点A可以知道节点B已经成功的接收了自己发送的MEET消息。
6)之后,节点A将向节点B返回一条PING消息。
7)节点B将接收到的节点A返回的PING消息,通过这条PING消息节点B可以知道节点A已经成功的接收到了自己返回的PONG消息,握手完成。
8)之后,节点A会将节点B的信息通过Gossip协议传播给集群中的其他节点,让其他节点也与节点B进行握手,最终,经过一段时间后,节点B会被集群中的所有节点认识。
作者:Lucien_168
链接:https://www.jianshu.com/p/0232236688c1
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
2. 集群使用了什么样的数据结构保存节点之间的状态信息?
clusterNode
clusterNode指的是某个节点的状态
// cluster.h
// 集群节点结构体
typedef struct clusterNode {
// 节点创建时间
mstime_t ctime;
// 节点ID,长度为40,每一个字符都是一个16进制字符,通过随机数生成
char name[REDIS_CLUSTER_NAMELEN];
// 节点标识,标识节点是 Master 或 Slave
int flags;
// 节点当前的配置纪元
uint64_t configEpoch;
// REDIS_CLUSTER_SLOTS:整个集群分块的总数目,即16384
// Slots是一个二进制位数组(bitarray),数组长度为REDIS_CLUSTER_SLOTS/8=2048个字节
// 位值1表示对应slot的数据存储在当前节点,是0表示不在这个节点。
unsigned char slots[REDIS_CLUSTER_SLOTS/8];
// 当前节点所分配的 slot 总数
int numslots;
// 若当前节点为 Master,则表示相应 Slave 的数据
int numslaves;
// 指针数组,指向 Slave 节点
struct clusterNode **slaves;
// 指正数组,指向 Master 节点
struct clusterNode *slaveof;
// 最近一次发送 ping包的时间
mstime_t ping_sent;
// 最近一次接受 pong包的时间
mstime_t pong_received;
// 最近一次被设置成 fail状态的时间
mstime_t fail_time;
// 最近一次为某个 Salve 投票的时间
mstime_t voted_time;
// 最近一次从这个节点接收到复制偏移量的时间
mstime_t repl_offset_time;
// 当前节点的复制偏移量
PORT_LONGLONG repl_offset;
// 当前节点的 IP
char ip[REDIS_IP_STR_LEN];
// 当前节点的 Port
int port;
// 保存连接相关的信息
clusterLink *link;
// 一个链表,记录了所有其他节点对该节点的下线报告
list *fail_reports;
} clusterNode;
clusterStates
clusterStates指的是当前节点在集群中所处的状态
// cluster.h
// 节点状态信息
typedef struct clusterState {
// 当前节点
clusterNode *myself;
// 集群当前的配置纪元
uint64_t currentEpoch;
// 状态标识:OK?FAIL?
int state;
// 集群中至少拥有一个 slot 的 Master 的数目
int size;
// 整个集群所有节点,键:节点ID,值:clusterNode结构体
dict *nodes;
// 节点黑名单
dict *nodes_black_list;
// 记录从当前节点迁移至目标节点的 slot,以及迁移的目标节点
// migrating_slots_to[i] = NULL 表示slot i未被迁移
// migrating_slots_to[i] = clusterNode_A 表示slot i要从本节点迁移至节点 A
// REDIS_CLUSTER_SLOTS:16384
clusterNode *migrating_slots_to[REDIS_CLUSTER_SLOTS];
// 记录要从源节点迁移到本节点的 slot,以及进行迁移的源节点
// importing_slots_from[i] = NULL 表示slot i未进行导入
// importing_slots_from[i] = clusterNode_A 表示正从节点 A中导入slot i
clusterNode *importing_slots_from[REDIS_CLUSTER_SLOTS];
// 记录各节点所分配的 slot
// 例如 slots[i] = clusterNode_A表示slot i 由节点 A处理
clusterNode *slots[REDIS_CLUSTER_SLOTS];
// 跳跃表
zskiplist *slots_to_keys;
// -------------------以下这些域被用于进行故障转移选举
// 上次执行选举或者下次执行选举的时间
mstime_t failover_auth_time;
// 节点收到的投票数目
int failover_auth_count;
// True:当前节点已经向其他节点发送投票请求
int failover_auth_sent;
// Slave 在当前故障转移选举中的排名
int failover_auth_rank;
// 当前选举的纪元
uint64_t failover_auth_epoch;
// Slave 不能执行故障转移选举的原因
int cant_failover_reason;
// -------------------共用的手动故障转移状态
// 手动故障转移时限
mstime_t mf_end;
// -------------------Mater 节点的手动故障转移状态
// Slave 节点
clusterNode *mf_slave;
// -------------------Slave 节点的手动故障转移状态
// 指示手动故障转移是否可以开始的标志值
PORT_LONGLONG mf_master_offset;
// 非0表示开始为选举 Master 投票
int mf_can_start;
// -------------------以下字段由 Master使用,用于记录选举状态
// 最近一次投票的纪元
uint64_t lastVoteEpoch;
// clusterBeforeSleep() 需要做的事情
int todo_before_sleep;
// 通过 cluster bus 发送消息的数目
PORT_LONGLONG stats_bus_messages_sent;
// 通过 cluster bus 接收消息的数目
PORT_LONGLONG stats_bus_messages_received;
} clusterState;

3. 槽指派的过程是什么样的?

命令:cluster addslots 0,1,2…5000
什么是槽
答:Redis Cluster中有一个16384长度的槽的概念,他们的编号为0、1、2、3……16382、16383。这个槽是一个虚拟的槽,并不是真正存在的。正常工作的时候,Redis Cluster中的每个Master节点都会负责一部分的槽,当有某个key被映射到某个Master负责的槽,那么这个Master负责为这个key提供服务,至于哪个Master节点负责哪个槽,这是可以由用户指定的,也可以在初始化的时候自动生成(redis-trib.rb脚本)。这里值得一提的是,在Redis Cluster中,只有Master才拥有槽的所有权,如果是某个Master的slave,这个slave只负责槽的使用,但是没有所有权。
记录节点的槽指派信息
clusterNode中有两个属性slots和numslots,slots是一个位图,长度为2048,一共有16384(2048*8)个插槽,如果某一位上为1,代表该clusterNode会处理这个槽。numslots代表该节点所处理插槽的数量。
clusterstates中同样有一个slots数组,但是和上面不同,slot类型是clusterNode,每个元素的值代表所指向的clusterNode。
传播节点的槽指派信息

一个节点除了记录自己负责处理的槽之外,还会将自己负责处理哪些槽的信息传播给其他节点,以此来告知其他节点自己目前负责处理哪些槽。
4. 来自客户端的命令请求是怎样被集群处理的
从客户端来看,集群应该是一个整体,具备高可用性、高性能。一个客户端向集群中的某个节点发起命令请求之后,集群会执行相应的查找,不一定会在当前节点被处理,有可能会重定向到其他节点。

- 客户端向集群发出一条消息。
- 当前节点中是否有所需要的插槽。
- 如果没有的话返回一个moved错误,并引导重定向到具有这个插槽的节点。
问题拓展
- 如何计算命令属于哪个插槽
- 如何判断槽是否由当前节点处理
- moved错误是怎么回事
5. moved错误和ask错误有什么区别
moved错误是一个转向的命令,在当前节点检查完自己的ckusterNode后发现自己不处理这个槽就会出现一个moved错误可以定位到处理这个槽的节点上去。
ask错误是在重新分片阶段,可能会出现的一种情况:属于被迁移槽的一部分键值对保存在源节点里面,而另一部分键值对则保存在目标节点里面。
6. redis集群中某个结点发生故障如何进行故障转移
为了保证集群的可用性,在搭建过程中一个节点往往都是一主一备甚至一主多备的模式,一旦某个主节点下线,其他几个从结点就会接管主节点的任务,通过选举算法选出一个新的主节点出来,后上线的主节点会变成新的主节点的从节点。
设置从节点
命令:cluster replicate node_id
向一个node_id发送命令,可以成为这个node_id的从节点
过程:
- 发送这个命令的节点会首先在自己的clusterState.nodes字典中找到node_id所对应节点的,然后将自己的clusterState.myself.slaveof指针指向这个结构,成为这个node_id节点的从节点
- 该节点修改clusterState.myself.flags中的属性为redis_node_slave
- 从节点开启复制功能,向主节点复制。于此同时,集群中的其他节点也会接收这个消息。
故障检测
过程:
- 节点A向节点B发送ping命令,如果没有收到节点B返回的pong命令,就会将这个节点标记为疑似下线的状态。
- 当一个主节点A通过消息得知主节点B认为主节点C进入疑似下线状态后,自己也会把这个节点标记为下线状态。
- 如果在一个集群里,半数以上的主节点都将主节点B标记为下线状态,那么就会将这个节点标记为已下线状态并在集群广播这条消息
故障转移
过程:
- 从节点中的一个节点会成为一个新的主节点
- 新的主节点会撤销对所有已下线主节点的槽指派并转向自己
- 发广播
新的主节点选举过程
- 集群中有一个配置纪元,本职是一个计数器,每当有一次故障转移发生,计数器就加1
- 对于每个配置纪元内,集群中每个负责处理槽的主节点都有一次投票的机会,第一个到达的从节点将获得该主节点的投票。
- 当节点发现自己正在复制的主节点下线时,从节点会向集群广播一条消息,要求所有的主节点给他投票,但是这些主节点只有一票,只有谁先到就给谁。
- 最后统计如果一个从节点的票数大于N/2+1(N是具有投票权的主节点个数),那么这个从节点称为新的主节点
- 如果当前的配置纪元没有选举出一个新的节点,那么进入一个新的配置纪元,并再次选举,知道选出新的主节点为止。