前言
我们这里首先来看 redis 这边实现比较复杂的 replication集群模式
我们这里主要关注的是 redis 这边的主从同步的相关实现
这边相对比较简单, 我们直接基于 cluster集群模式 进行调试
主从命令同步复制
比如这里 master 是 redis_7002, slave 是 redis_7005
然后 这里是 master 这边直接同步 相关命令到各个 slave
然后这里是在 server.call:3765 进行命令存储到 backlog, aof, 以及同步发送到 slave
命令真实执行是在 server.call:3675 的 cmd->proc->proc(c)
然后 redis_7005 这边拿到命令之后, 开始执行, 这个执行过程就和客户端这边发送执行命令一样
全量同步
整个流程分为两个 part, 一个是请求方, 就是 slave, 一个是提供方, 是 master
slave 这边连接到 master 之后, slave 发送 SYNC 命令到 master
master 通知客户端需要全量同步, 然后 master 这边后台去 dump 内存快照文件
slave 这边等待 master 这边 dump 内存快照文件, 并通过网络传递过来
master 这边 dump 内存快照文件生成之后, 会向 slave 这边进行传输
slave 这边拿到 master 的 dump 的内存快照信息, 将其持久化到 临时文件, 完成之后重命名为 dump.rdb, 然后清空当前数据库, 然后 加载 dump.rdb 文件进行一个快照的数据恢复
我们这里 一边一边的看, 先看 slave, 再看 master
slave 的处理
这个是 slave 这边启动的时候 就会进行的一个操作
这里 syncWithMaster 根据 server.repl_state 这边同步的处理了几个 slave 和 master 的交互, 分为了几个不同的阶段 REPL_STATE_CONNECT, REPL_STATE_CONNECTING, REPL_STATE_RECEIVE_PING_REPLY, REPL_STATE_SEND_HANDSHAKE 等等
比如这里会 发送 ping 给 master, 然后等待 master 这边回复信息
在发送 masterauth 等相关信息, REPLCONF 等相关信息
后面和全量同步相关比较重要的流程就是 发送 SYNC 相关命令
然后 slave 这边会收到 master 这边的 FULLRESYNC 的通知
然后就是后面 slave 这边创建临时文件, 然后准备接受 master 这边 后台持久化 rdb 文件之后传递给 slave 这边, 之后的处理交给 readSyncBulkPayload
接受服务器这边传输过来的 rdb 文件的相关信息, 持久化到 上面创建的临时文件 repl_transfer_tmpfile
然后就是 清空 slave 本地数据库, 然后重命名 临时文件 为 dump.rdb
然后加载 dump.rdb 到 slave 本地数据库
触发上面 syncWithMaster 的地方, 从上下文是可以看到是 serverCron 注册了一个定时任务, 100ms 跑一次, 跑的任务是 clusterCron 相关, 里面包含了这里的 connectWithMaster
所以节点的上下线 是可以再 100ms 左右感知到的
客户端这边相关日志如下, 基本上是 syncWithMaster + readSyncBulkPayload 这两部分中输出的
前面两行是 connectWithMaster 中输出的连接 master 的相关日志
后面的是 slave 这边向 master 发送 PING, master 进行回复
后面的是 slave 这边发送 SYNC 指令, 询问是否可以增量同步, master 这边回复 只能全量同步
然后是 slave 这边接收到了 master 的 dump.rdb 的数据信息, 合计210字节, 清空 slave 的数据库, 从 dump.rdb 中加载数据到 slave 的数据库, 最终同步成功
master 的处理
需要全量同步的有几个场景
场景1 是 slave 未传递同步偏移, 或者偏移传递错误
场景2 是 master 和 slave 两边的 replyid 对不上
场景3 是 slave 这边传递的偏移不在 master 这边数据同步的偏移区间内, 偏移错误 或者 落后的太多
然后就是 master 这边处理全量同步的大头了
场景1 如果是已经在进行 dump 内存快照文件, 则等待即可
场景2 如果是已经在 dump 内存快照, 并且通过 tcp 直接传输数据, 则只能等待操作完成
场景3 master 可以 dump 内存快照, 或者直接通过 tcp 传输数据,
处理如下, 通过 startBgsaveForReplication 进行逻辑上的分发
是 dump 内存快照文件, 还是 dump内存快照直接通过 tcp 传输数据
然后这里就是 fork 出子进程, 让子进程 来 dump 内存快照, 并保存到目标文件
然后 master 这边有定时任务在定时检查 子进程是否完成, 周期为 1秒
如果完成了, 则向 slave 这边传输 dump 的内存快照的数据
master 这边日志如下
slave 这边发送过来了 PSYNC 的请求
然后 master 这边判断 replyid, replyid2 匹配不上, 然后开始进行 全量同步
然后 master 开始进行 dump 内存快照, fork 子进程 来生成 dump.rdb
master dump 内存快照生成完成之后, 发送数据到 slave, 之后记录 成功日志
基于 backlog 的增量同步
master 这边添加增量命令到 backlog
命令到 server.call:3765 的时候, 会将命令添加到 backlog 队列
其中存储的数据就是 具体的执行命令, 存储在了一个数据中, 比如这里的 “set name4 jerry17”, 会将其存储为 一个长度为3的数组, 元素分别为 [“set”, “name4”, “jerry17”]
具体的复制命令到 backlog 中的相关处理如下, 可以简单理解为一个 memcpy 进去, 然后更新 索引 repl_backlog_idx
slave 这边长时间和 master 失联, 重新连接之后发送 PSYNC 增量同步请求
clusterCron 为定时任务, 定时扫描集群的状态, 如果失联了, 则重新建立连接
同样是在 syncWithMaster 的代码中, 这里传入了 replyid 和 offset 到 master
master 这边获取增量的命令响应给客户端
比如这里传输了 134 字节的命令数据到客户端, 我们 再来看一下 详细的命令信息
从响应的字符串信息中可以看到, 传输了 “select 0”, “set name4 jerry18”, “set name4 jerry19”, “set name4 jerry20” 四条命令, 其中 “select 0” 是 redis 自己增加的, 然后 其他的几个命令是 客户端这边交互产生的
slave 这边收到命令之后执行
客户端这边接收到这一批次的命令之后, 依次进行执行 “select 0”, “set name4 jerry18”, “set name4 jerry19”, “set name4 jerry20” 进而实现了增量的同步
完