在Redis中,用户通过执行slaveof 命令 或者设置slaveof 选项,实现主从复制。
两台实例:127.0.0.1:6379 127.0.0.1:12345 建立主从复制关系
# 建立主从关系 127.0.0.1:12345> SLAVEOF 127.0.0.1 6379 OK # 主库新建键值 127.0.0.1:6379> set msg "hello world" OK # 从库与主库数据一致 127.0.0.1:12345> get msg "hello world"
旧版本功能的实现
redis的复制功能分为同步(sync)和命令传播(command propagate)两个操作:
- 同步
- 用于主从数据库当前状态的同步
- 类似MySQL在建立主从复制前,从库是由主库的备份恢复而来。
- 命令传播
- 在主库状态被修改,使主从同步
同步
当从库使用SLAVEOF命令时,则首先做同步工作;
而做同步的实际命令是sync,
sync的执行步骤:
- 从库向主库发送sync命令
- 收到sync命令主服务器执行BGSAVE,后台生成RDB文件,并使用缓存区记录从现在开始的所有写命令
- 主完成BGSAVE,将RDB文件 发送给从库,从库接收并载入这个RDB文件
- 相当于用主库的备份恢复成从库
- 主库将缓存区的所有写命令发送给从库,从库执行这些写命令,以实现主从同步;
sync执行过程:
命令传播
主库将自己执行的写命令,发送给从库,从库也会去执行使得主从同步。
旧版复制功能的缺陷
主从复制分为两种情况:
- 初次复制
- 从库没复制过任何主库
- 从库当前要复制的主库和上次复制的主库不同
- 断线后重复制
- 处于命令传播阶段的从主,因网络原因中断复制
- 从库通过自动重连,连上了主库,并重来一遍:同步->命令传播
对于初次复制,旧版已经能很好的完成任务;
对于断线后重复制,旧版虽功能可以实现,但是效率极低:
- 因为重连之后又要重新来一遍同步,这是相当低效率的
- 因为数据在断线的时间段,改变很小,当却要将整个数据重新同步
新版复制功能的实现
从redis 2.8 开始使用PSYNC 替代SYNC命令来执行复制时的同步操作
PSYNC具有完整同步和部分同步
- 完整同步
- 适用于初次复制情况,和sync命令执行步骤基本一样
- 部分同步
- 适用于断线后重复制情况
- 断线重连接后,主库仅将断线时间内,所执行的命令发送给从库
主从部分同步过程:
部分同步的实现
psync部分同步功能由以下三个部分构成:
- 主库复制的偏移量(replication offset) 和从库的复制偏移量
- 主库的复制积压缓冲区(replication backlog)
- 服务器的运行ID(run ID)
复制偏移量
主从双方都会维护一个复制偏移量:
- 主库每次向从库传播N个字节数据时,就将自己的复制偏移量的值加上N
- 从库每次接受N个字节数据时,也将复制偏移量值加N
断线状态:
在主库发送33字节数据,前A断线
假设A断线之后立即重连成功,那么从库向主库发送PSYNC命令,报告当前A的复制偏移量为10086
,执行部分同步,主库是如何补偿A在断线期间丢失的那部分数据?
这和复制积压缓冲区有关。
复制积压缓冲区
复制积压缓冲区是由主库维护的一个固定长度(fixed-size)先进先出(FIFO)队列,
默认大小为1MB
问题:如果从库断线时间很长,长到主库复制积压缓冲区 都已经溢出了,这时主从将如何实现数据同步?
- 猜测应该在从库连上来之后,执行部分同步,然后同步完成之后发现,复制偏移量还是不一样,
- 说明复制积压缓冲区在断线期间有溢出,则应该重新来一次完全同步,来实现主从同步。
- 实际是判断 offset,
- 如果从库的offset 存在于复制积压缓冲区,则将offset+1 往后的数据发送给从库,执行部分同步
- 问题:假设一条命令有10个字节,而缓冲区,只剩下了前5个字节,后5个字节已经丢失
- 此时从库offset值是存在于主库缓冲区,也开始执行部分同步,从库只收到这条命令的前5个字节,同样会导致主从不一致?此时必然造成主从不同步?那么redis有没有去感知这种情况?又是如何处理这种情况的???
- 如果从库的offset 不存在于复制积压缓冲区,则执行完全同步
- 如果从库的offset 存在于复制积压缓冲区,则将offset+1 往后的数据发送给从库,执行部分同步
当主库传播命令时,它不仅会将写命令发送给所有从服务器,还会将命令写到缓冲区;
服务器运行ID
实现部分重同步还需要用到服务器运行ID(run ID):
- 每个Redis服务器,不论主服务器还是从服务器,都会有自己的运行ID
- 运行ID在启动时自动生成
- 初次复制时,从库保存主库的run ID
- 当从库断线并重连上一个主库时,从库将之前保存的主库run ID 发送给现在连接的主库
-
- 如保存的run ID 与现在主库的ID 相同,则主库尝试执行部分重同步操作
- 否则,则说明主库不是之前的主库。则执行完整重同步操作
PSYNC 命令的实现
- 如果从库没有复制过任何主库,或者之前执行过 SLAVEOF no one 命令
- 则从库开始新的一次复制时 将向主库发送PSYNC ? -1 命令
- 主动请求主库进行完整重同步
- 相反,如果从库已经复制过某主库
- 则从库开始新一次复制时,向主库发送 PSYNC <runid> <offset>命令
- 主库通过这两个参数来判断应该对从库执行哪种同步操作。
- 根据情况,主库接收到PSYNC 会返回以下 三种回复中的一种:
- +FULLRESYNC <runid> <offset>
- 执行完整重同步操作
- +CONTINUE
- 执行部分重同步操作
- -ERR
- 主库版本低于Redis 2.8 它识别不了PSYNC 命令
- +FULLRESYNC <runid> <offset>
流程图
复制的实现
步骤一:设置主库的地址和端口
SLAVEOF <master_ip> <master_port>
通过上面命令将主库的IP 和 端口 写到从库的服务器的状态中
步骤二:建立套接字连接
套接字连接完成之后
- 从库为这个套接字关联一个专门用于处理复制工作的文件事件处理器。
- 主库,为套接字创建相应的客户端状态,并将从库看作是一个连接到主库的客户端来对待
步骤三:发送ping 命令
从库成为主库的客户端后,做的第一件事就是向主库发送ping命令。
ping命令作用
- 使用ping命令 检查套接字读写状态是否正常
- 因复制工作,基于主库正常处理命令请求,通过ping检查主库能否正常处理命令请求
- 从库ping命令将遇到以下三种情况的一种:
- 如主库返回了一个回复,但从库不能再规定时间(timeout)内读取命令的内容
- 说明主从之间网络连接状态不佳,不能继续复制工作。
- 从库断开连接,并重新创建套接字
- 说明主从之间网络连接状态不佳,不能继续复制工作。
- 如主库返回一个错误
- 说明主库无法处理从库命令请求,不能继续复制工作。
- 从库断开连接,并重新创建套接字
- 说明主库无法处理从库命令请求,不能继续复制工作。
- 如从库读取到PONG,回复
- 说明一切正常 准备执行下一个操作
- 如主库返回了一个回复,但从库不能再规定时间(timeout)内读取命令的内容
- 从库ping命令将遇到以下三种情况的一种:
从库发送ping可能遇上的情况
步骤4:身份验证
如从库设置了masterauth 选项则进行身份验证,否则不验证。
如进行身份验证
从库发送一条AUTH 命令,命令参数为从库masterauth 选项的值
从库在身份验证阶段可能遇到的情况
步骤5:发送端口信息
在完成身份验证步骤之后,从库执行 REPLCONF listening-port <port-number>,向主库发送从库监听的端口号。
例如,从库的监听端口号为12345
步骤6:同步
从库向主库发送PSYNC命令,执行同步操作,并将自己数据库更新至主服务器当前所处的状态。
步骤7:命令传播
当完成同步之后,主从进入命令传播阶段,这是主库只要一直将自己执行的写命令发送给从库,从库接收并执行主库发来的命令
只可以保证主从服务器数据一致了。
心跳监测
在命令传播阶段,从库默认每秒一次的频率,向主服务器发送命令:
REPLCONF ACK <replication_offset>
其中replication_offset 是从库当前的复制偏移量
RREPLCONF ACK 对于主从的三个作用
- 检测主从服务器的网络连接状态
- 辅助实现min-slaves选择
- 检测命令丢失
检测主从网络状态
主库超过一秒没有收到从库的REPLCONF ACK命令,则主库就知道主从网络连接出现问题了。
主库通过INFO replication 命令 ,查看从库状态,lag 表示 最近一次 发送REPLCONF ACK 距离现在的时间。
辅助实现min-slaves 配置选项
Redis的min-slaves-to-write 和 min-slaves-max-lag两个选项可以防止主服务器在不安全的情况下执行写命令。
例子
min-slaves-to-write=3
min-slaves-max-lag=10
那么在从库的数量少于3个,或者三个从库的延迟(lag)值都大于或等于10秒时,那么主库将拒绝执行写命令。
这里延迟就是上面INFO replication 命令的lag值。
检查命令丢失
主库接收从库通过 REPLCONF ACK <replication_offset> 发送来的复制偏移量,
与自己的偏移量相比,如果发现从库偏移量少于自己的偏移量,则在复制积压缓冲区里
找到从库缺少的数据,并将这些数据重新发送给从服务器。
注意:主服务器向从服务器补发缺失数据这一操作的原理和部分重同步操作的原理非常相似,这两个操作的区别在于
补发缺失数据操作在主从服务器没有断线的情况下执行,而部分重同步操作则在主从服务器断线并重连之后执行。
问题:
主库命令一由于网络原因丢失,但随后命令二,从库接收到了。
此时从库offset 已经更新了,不再是丢失命令一时的值。那么此时
Redis 有没有什么办法发现 命令一丢失了?或者说是如何处理这种异常的?