sentinel模式是在一主多从情况下做高可用的,redis集群中,使用集群自己的方式做高可用
节点
- 集群模式下的redis,会使用单机模式下所有的服务器组件,继续使用redisServer结构体保存服务器状态,而集群模式下使用的数据,会放到
clusterNode
、clusterLink
、clusterState
三个结构体中,集群的定时任务函数clusterCron
会在serverCron
定时函数内调用。 - 节点之间相互加入,需要使用命令
CLUSTER MEET <ip> <port>
手动添加
clusterNode结构体
- 保存一个节点的当前状态:创建时间、名称、配置纪元、ip port等。每一个节点都会用一个clusterNo的结构体来记录状态,并为其他集群中的节点创建一个相应的clusterNode存在clusterState结构体中。
clusterLink结构体
- clusterNode结构体内使用,用来并且坳村连接节点的相关信息,如套接字描述符,输入缓冲区和输出缓冲区
clusterState结构体
- 记录当前节点视角下,集群所处的状态,如是否上线,包含节点数,配置纪元等。clusterState中会包含一个字典,其中记录集群所有节点,键为节点名称,值为clusterNode结构表示的节点
集群分片
- redis实现的集群,将集群的数据库分为16384个槽,只有当所有槽都分配给节点之后,集群才认为是上线状态。每个节点可以处理0~16384个槽。
- 使用命令
CLUSTER ADDSLOTS <slot [slot]>
可将槽分配给当前节点
槽信息存储
- clusterNode内有一个char数组
slots[16384/8]
按位存储当前节点负责哪些槽,另有一个numsoltes
计数器,记录当前有多少个槽分派了。 - 此外,在clusterState结构体内,有一个clusterNode数组
slots[16384]
,记录集群所有槽的指派信息。 - 集群的节点之间,会发送各自负责的槽的信息,用于更新clusterState内的槽相关信息。
- clusterState内尽管存储了集群所有节点的槽信息,但是使用clusterNode存储当前节点的槽信息依旧是有必要的,因为当节点广播自身的槽指派信息时,只需要clusterNode的信息即可,更易操作。
计算键属于哪个槽
- 使用CRC16(key) & 16383,通过 & 来做模运算,确定key所属的槽
集群分片后键值的操作
- 计算键的归属
- 通过clusterState的slots数组,得到槽所属服务器clusterNode
- 判断当前节点是否正是槽所属的服务器,如果是,处理命令;如果不是,返回moved错误和正确的服务器ip port:
MOVED <solt> <ip> <port>
。该错误在集群模式下的客户端会自动解析并跳转到正确服务器再次执行命令。
集群分片后,数据库使用的不同
- 集群模式下,服务器只使用0号数据库
- clusterState还会使用跳跃表
slots_to_keys
存储所有key和槽的对应关系,用于一些统计槽信息的命令,例如CLUSTER GETKEYSINSLOT <slot> <count>
返回属于slot的最多count个键的命令
重新分片:使用redis-trib redis集群管理软件
- 对目标节点发送
CLUSTER SETSLOT <slot> IMPORTING <source_runId>
,让目标节点准备从源节点导入槽信息,这时clusterState内的clusterNode数组importing_slots_from[16384]
的slot下标元素,会设为源节点 - 对源节点发送
CLUSTER SETSLOT <slot> MIGRATING <target_runId>
,让源节点准备向目标节点迁移槽下的键值对,这是clusterState内的clusterNode数组migrating_slots_to[16384]
的slot下标元素,将会设置为目标节点 - 向源节点发送命令
CLUSTER GETKEYSINSLOT <slot> <count>
,得到属于slot槽的最多count个键值对的键 - 对
3
中获取的每个键,redis-trib向源节点发送命令MIGRATE <target_ip> <target_port> <key_name> 0 <timeout>
,向键向目标节点迁移 - 重复
3-4
步骤 - redis-trib向集群任意节点发送命令
CLUSTER SETSLOT <slot> NODE <target_runId>
,声明新节点处理的槽,这个信息会通过集群的消息机制发送到整个集群中。
重新分片期间的键值对操作
- 重新分片期间的请求,会发到源节点上,这个时候,源节点可能因为key已经迁移到目标节点的原因,找不到key时,首先检查migrating_slots_to[16384]数组对应slot是否为空,不为空则返回ASK错误
ACK <slot> <ip> <port>
,
redis客户端接收后,会先打开客户端的REDIS_ASKING
标识,再次向目标服务器请求,目标服务器检查到ASKING标识后,无论自己当前负不负责key所在的槽,都将执行一次操作。
ASK错误和MOVED错误
- ASK错误是临时的,而MOVED错误是永久的,当客户端遇到MOVED错误后,以后所有对应槽的操作,都会去往MOVED错误指定的节点上,而遇到ASK错误之后,以后对应槽的操作,还是会去往以前访问的服务器
集群复制和故障转移
集群的复制和故障转移是自己实现的,没有使用sentinel哨兵,但是和sentinel的流程类似。
集群的复制
- 一个集群内,可以有一部分是主服务器,另一部分是从服务器,复制主服务器
- 通过使用命令
CLUSTER REPLICATE <target_runId>
将执行命令的服务器设置为目标节点的从服务器。这将导致
- clusteNode内的clusterNode属性
slaveof
指向目标节点 - clusterState内属性
myself.flags
从REDIS_NODE_MASTER
更改为REDIS_NODE_SLAVE
- 从服务器的信息通过集群的消息机制,传播到整个集群中
- 收到消息的集群节点,会在所有表示主节点的clusterNode结构体内,更新两个属性:整数
numslaves
表示的当前节点下的从服务器数量,和clusterNodel类型的数组slaves
表示当前节点下的所有从服务器
- clusteNode内的clusterNode属性
集群的故障检测
- 和sentinel类似,集群节点之间,也会定期发送PING命令,如果没有在规定时间内返回PONG,则认为目标节点疑似下线(PFAIL,和sentinel的主观下线类似),打开对应的
REDIS_NODE_PFAIL
标识 - 当一个节点认为某一个节点疑似下线后,在之后发送消息交换集群各个节点状态信息的时候,将会将这个消息传出去
- 集群其他节点接受到疑似下线的消息,更新疑似下线节点clusterNode内表示下线报告的链表
fail_report
(链表成员为clusterNodeFailReport
). - 当半数一上服务器认为目标节点为疑似下线节点后,目标节点被认定为已下线(FAIL,类似sentinel的客观下线,只是评判标准可能不同),这时节点会广播FAIL消息到集群,向集群告知某个节点已下线
集群的故障转移
- 选举新的主节点,还是使用Raft算法,所有主节点拥有投票权,所有已下线节点的从节点向它们请求将自己设为主节点,如果其他主节点回复
CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK
,则认为其他主服务器接受将发送请求的从服务器设为新的主节点,当超过半数的主节点选择了同一个从节点后,该从节点被选举为新的主节点,否则,配置纪元+1,再次选举 - 被选中从节点执行
SLAVE no one
命令,称为主节点 - 新的主节点撤销所有对已下线主节点的槽指派,并将其指派给自己
- 新的主节点向集群广播PONG消息,让集群其他节点知道当前节点成为了新的主节点
- 一个疑问,这里的从节点选主,不需要判断从节点的复制进度吗?
集群的消息
- 消息分为5种:MEET消息、PING消息、PONG消息、FAIL消息、PUBLIST消息
- 消息的构成:消息头和消息正文。消息头包含消息正文、消息的类型、消息的长度、消息发送者的信息
- MEET,PING和PONG信息实现:使用Gossip协议。发送消息的时候会随机选举当前节点已知的两个其他节点的信息,放到消息主体中,发给目标节点。目标节点接收到消息,访问消息主体,对两个其他节点的信息或者更新(如果这两个节点目标节点已知),或者握手(如果这两个节点目标节点未知)
- FAIL消息的实现:消息主体包含的是已下线服务器的独一无二的名称,这个消息会广播尽快告知其他节点
- PUBLISH消息:当客户端发送命令
PUBLISH <channel> <message>
后,除了接收到命令的节点会向channel发送消息,节点还会广播一条publsh消息,所有接收到消息的节点会再次向channel发送消息