主从库与切片集群机制
主从复制源码剖析
redis的主从复制主要包括全量复制RDB文件,增量复制,长连接同步,使用了基于状态机的设计思想,来实现不同状态和状态间的跳转
基于状态机实现的话,在开发程序时只需要考虑不同状态下具体要执行的操作,以及状态之间的跳转条件即可
四大阶段
初始化阶段:将实例A设置为B的从库,获取主库的ip和port
建立连接阶段:实例A尝试与主库建立TCP连接,并在连接上监听主库的命令
主从握手阶段:主从库间相互发送 PING-PONG 消息,同时从库根据配置信息向主库进行验证
复制类型判断和执行阶段:主库会根据从库发送的命令参数作出相应的三种回复,分别是执行全量复制、执行增量复制、发生错误。最后,从库在收到上述回复后,就会根据回复的复制类型,开始执行具体的复制操作
状态机实现
在每个redis实例里都对应着一个redisServer结构体,在其中与主从复制状态机相关的变量是repl_state
初始化阶段
实例启动后,把状态机初始状态设置为REPL_STATE_NONE,而一旦执行了replicaof masterip masterport之后,会设置主库IP和端口号,并把状态机设置为REPL_STATE_CONNECT,完成初始化阶段
建立连接阶段
redis会每隔1000ms调用replicationCron()执行任务,检查复制状态机状态为REPL_STATE_CONNECT的话就开始和主库建立连接,然后在连接上创建读写事件并且注册处理读写事件的函数syncWithMater,然后将从库状态机设置为REPL_STATE_CONNECTING
主从握手阶段
一旦主从库间连接建立,从库实例中的syncWithMaster函数被回调,在该函数中如果从库实例状态为REPL_STATE_CONNECTING,就会发送PING消息给主库,并设置状态机为REPL_STATE_RECEIVE_PONG
然后从库会依次发送验证信息、端口号、IP、对RDB文件和无盘复制的支持情况,每一次握手发送信息时都对应着从库的一组状态变迁(发送前是SEND状态,发送完成后是RECEIVE状态并开始读取主库返回的结果)
复制类型判断与执行阶段
完成握手之后,从库状态变迁为REPL_STATE_SEND_PSYNC,表明开始向主库发送PSYNC命令,开始实际的数据同步,当调用函数完成发送之后又变迁为REPL_STATE_RECEIVE_PSYNC,这个调用函数会负责向主库发送数据同步的命令,并根据主库的回复消息将返回值置为不同结果
然后syncWithMaster根据返回值的不同执行不同处理,比如返回FULLRESYNC,从库会在和主库的网络连接上注册readSyncBulkPayload函数,并将状态机设置为REPL_STATE_TRANSFER,表明开始实际的数据同步
哨兵和raft
redis哨兵leader选举实现时涉及到raft协议,区别在于:在正常运行时,不同实例间不是leader和follower的关系,而是对等的关系
实现哨兵实例工作主题逻辑的函数:sentinelHandleRedisInstance函数,周期性执行检测哨兵监听节点的状态
- 重建连接:尝试和断连的实例重建连接
- 发送命令:向实例发送ping、info等命令
- 判断主观下线:检查监听的实例是否主观下线
- 判断客观下线和执行故障切换:首先真对监听的主节点判断是否客观下线;接着判断是否要启动故障切换,如果需要就再获取其他哨兵对主节点状态的判断,并向其他哨兵发送is-master-down-by-addr命令发起leader选举,随后执行故障切换,最后再获取对新主节点的状态判断
判断主节点是否客观下线的函数逻辑:通过遍历监听同一主节点的其他哨兵的 flags 变量,来判断主节点是否客观下线的。
随后判断是否要进行故障切换的条件有三个:
- 主节点的 flags 已经标记了 SRI_O_DOWN;
- 当前没有在执行故障切换;
- 如果已经开始故障切换,那么开始时间距离当前时间,需要超过 sentinel.conf 文件中的 sentinel failover-timeout 配置项的 2 倍。
同时sentinelAskMasterStateToOtherSentinels用于给其他哨兵发送 sentinel is-master-down-by-addr,命令包括主节点ip,主节点port,当前纪元和实例ID,如果实例ID不是*,则会调用sentinelVoteLeader进行哨兵的Leader选举
假设哨兵A判断主节点master客观下线,它向B发起投票请求,B执行sentinelVoteLeader时会判断A、B、master记录的leader纪元,通过纪元来进行轮次记录从而让follower在一轮中只能投一票,只有master的leader纪元小于哨兵A纪元,且B纪元小于等于A纪元,保证B还没有投过票,才能给A投票
最终leader当选的条件是:获得超过半数的,且超过预设的quorum阈值的其他哨兵赞成票