第 15 章 复制
执行 SLAVOF 命令或设置 slaveof 选项,让一个服务器去复制另外一个服务器,被复制的为主服务器,对主服务器进行复制的是从服务器
进行复制的主从服务器双方的数据库将保存相同的数据,即数据库状态一致,简称“一致”
15.1 旧版复制功能的实现
复制功能分为同步和命令传播两个操作
-
同步操作作用于服务器的数据库状态更新至主服务器当前所处的数据库状态
-
命令传播则作用于在主服务器的数据库状态被修改,导致主从服务器的数据库状态出现不一致时,让主从服务器的数据库重新回到一致状态
15.1.1 同步
客户端向从服务器发送 SLAVEOF 命令复制主服务器时,首先从服务器执行的是同步操作,更新至主服务器当前所处的数据库状态
实际操作是从服务器向主服务器发送 SYNC 命令:
- 从服务器向主服务器发送 SYNC 命令
- 收到 SYNC 命令的主服务器执行 BGSAVE 命令,在后台生成一个 RDB 文件,并使用一个缓冲区记录从现在开始执行的所有写命令
- 当主服务器的 BGSAVE 命令执行完,主服务器会将 BGSAVE 命令生成的 RDB 文件发送给从服务器,从服务器收到后载入 RDB 文件,更新至主服务器执行 BGSAVE 的状态
- 主服务器将记录在缓冲区里的所有写命令发送给从服务器,从服务器执行写命令,更新指服务器所处的状态
15.1.2 命令传播
同步操作之后,主从服务器两者的数据库状态将达到一致状态,但当主服务器执行客户端的命令时,主服务器的状态可能修改,不再一致
为了让主从服务器再一次回到一致状态,主服务器需要对从服务器执行命令传播操作:
将造成主从服务器不一致的写命令发送给从服务器执行,执行后再一次回到一致状态
15.2 旧版复制功能的缺陷
从服务器对主服务器的复制可以分为以下两种情况:
- 初次复制:从服务器没有复制过任何主服务器,或者从服务器当前要复制的主服务器和上一次复制的主服务器不同
- 断线后重复制:处于命令传播阶段的主从服务器因网络原因中断了复制,但从服务器通风自动重连接重新连上了主服务器,并继续复制主服务器
初次复制使用旧版复制功能你那个很好地完成任务,但是对于断线后重复制来说,旧版复制功能效率低
在命令传播阶段某个时间点断线后主服务器执行了部分命令修改了数据库状态,而从服务器还在重连,成功后是执行 SYNC 命令重新开始操作,但是实际上不是很需要的,因为主从服务器在断线之前是一致状态的
SYNC 命令
- 主服务器执行 BGSAVE 命令生成 RDB 文件,耗费主服务器 CPU、内存、磁盘 I/O 资源
- 发送 RDB 文件耗费主从服务器大量的网络资源(带宽和流量),并对主服务器响应命令请求时间产生影响
- 从服务器需要载入 RDB 文件,进入阻塞状态无法处理命令请求
15.3 新版复制功能的实现
使用 PSYNC 命令代替 SYNC 命令解决断线重复制的低效问题,
PSYNC 命令有完整重同步和部分重同步两种模式:
- 完整重同步:初次复制情况,步骤和 SYNC 命令执行步骤一致
- 部分重同步:断线后重复制情况,主服务器将断线期间执行的写命令发送给从服务器,从服务器接收并执行,更新至服务器当前状态
15.4 部分重同步的实现
部分重同步功能由以下三个部分构成:
- 主服务器的复制偏移量和从服务器的复制偏移量
- 主服务器的复制积压缓冲区
- 服务器的运行 ID
15.4.1 复制偏移量
主服务器和从服务器会分别维护一个复制偏移量:
- 主服务器每次向从服务器传播 N 个字节的数据时,就将自己的复制偏移量的值加上 N
- 从服务器每次收到主服务器传播的 N 个字节数据时,就将自己的复制偏移量加上 N
通过对比偏移量可以知道主从服务器是否处于一致状态
15.4.2 复制积压缓冲区
复制积压缓冲区是由主服务器维护的一个固定长度先进先出队列,默认为 1 MB
当主服务器进行命令传播时,不仅会将写命令发给所有从服务器,还会将写命令入队到复制积压缓冲区里面,且队列中每个字节记录相应的复制偏移量
当从服务器重新连上主服务器后,从服务器会通过 PSYNC 命令将自己发复制偏移量 offset 发送给主服务器,主服务器根据此来决定对服务器执行何种同步操作:
- 如果 offset 之后的数据仍在复制积压缓冲区中,则进行部分重同步操作
- 相反,已经不存在复制缓冲区中则进行完整重同步操作
根据需要调整复制缓冲区大小
默认为 1 MB,最小跟由公式
second * write_size_per_second
估算
- second:服务器断线后重新连上主服务器的平均时间,单位秒
- write_size_per_second:服务器平均每秒产生的写命令数据量(协议格式的写命令的长度总和)
安全起见,实际大小可以设置为:2 * second * write_size_per_second,可以保证绝大部分断线情况都能用部分重同步处理
15.4.3 服务器运行 ID
- 每个服务器都有自己的运行 ID
- 运行 ID 在服务器启动时自动生成,由 40 个随机的十六进制字符组成
当从服务器进行初次复制时,主服务器会将自己的运行 ID 传送给从服务器,而从服务器会将其保存,当从服务器断线并重新连上一个主服务器时,从服务器将当前连接的主服务器发送之前保存的运行ID:
- 如果 ID 与当前连接的主服务器 ID 相同,则说明断线之前是这个主服务器与之连接,则继续尝试部分重同步操作
- 否则则不同,断线之前不是这个主服务器,执行完整重同步操作
15.5 PSYNC 命令的实现
PSYNC 命令的调用方法有两种:
- 如果从服务器没有复制过任何主服务器,或者之前执行了 SLAVEOF no one 命令,则在开始一次新的复制时向主服务器发送 PSYNC ? -1 命令,主动请求主服务器进行完整重同步
- 否则发送 PSYNC
命令 - runid:上一次复制的主服务器 ID
- offset:从服务器当前复制偏移量
- 服务器根据这两个参数决定对从服务器执行什么同步操作
服务器会返回以下三种回复之一:
- + FULLRESYNC
回复,主服务器将与从服务器进行完整重同步操作 - runid:主服务器 ID,从服务器保存并作为下一个 PSYNC 参数使用
- offset:主服务器当前复制偏移量,从服务器为将其作为自己的初始化偏移量
- + CONTINUE 回复:主服务器将与从服务器执行部分重同步操作
- - ERR 回复,主服务器版本低于 2.8 识别不了 PSYNC 命令,需要从服务器发送 SYNC 命令,执行完整同步操作
15.6 复制的实现
SLAVEOF <master_ip> <master_port>
15.6.1 步骤1:设置主服务器的地址和端口
从服务器将客户端输入的主服务器的 IP 地址和端口保存到服务器状态的 masterhost 和 masterport 属性中
SLAVEOF 是一个异步命令,完成以上两个属性的设置之后,从服务器向客户端返回 OK,表示复制指令已经被接收,但实际的复制工作将在 OK 之后才真正执行
15.6.2 步骤2:建立套接字连接
根据 IP 地址和端口,从服务器创建连向主服务器的套接字连接,如果连接成功,从服务器将为此套接字关联一个专门用于处理复制工作的文件事件处理器,负责后续的工作
主服务器在接收从服务器的套接字之后为该套接字创建相应的客户端状态,并将从服务器看作一个连接到主服务器的客户端看待,此时从服务器将具有服务器和客户端两种状态:从服务器可以向主服务器发送命令请求,而主服务器则会向从服务器返回命令回复
15.6.3 步骤3:发送 PING 命令
从服务器成为客户端后第一件事是向主服务器发送一个 PING 命令
- 检查套接字的读写状态是否异常
- 检查主服务器能否正常处理命令请求
从服务器将遇到三种情况:
- 主服务器返回命令回复,但从服务器不能在规定时间内读取命令回复的内容,则表示主从服务器之间的网络连接状态不稳定,需要从服务器断开并重新创建套接字
- 主服务器返回错误。表示主服务器无法处理从服务器命令请求,则从服务器断开并重新创建套接字
- 从服务器读到 “PONG” 回复,表示主从服务器状态正常,可以进行后续操作
15.6.4 步骤4:身份验证
下一步则是决定是否身份验证:根据是否设置了 masterauth 选项
身份验证:
从服务器向主服务器发送了一条 AUTH 命令,参数为从服务器 masterauth 选项的值
可能遇到几种情况:
- 主服务器没有设置 requirepass 选项,从服务器也没有设置 masetrauth 选项,主服务器继续执行从服务器发送的命令
- 从服务器通过 AUTH 发送的密码和主服务器 requirepass 选项设置的密码相同,继续执行,否则则返回 invalid password 错误
- 主服务器有 requirepass 选项,从服务器无 masetrauth 选项,主服务器返回 NOAUTH 选项
- 主服务器无 requirepass 选项,从服务器有 masetrauth 选项,主服务器返回 no password is set 错误
错误情况令从服务器中止目前的复制工作,并从创建套接字重新开始,直到身份验证或者从服务器放弃执行复制
15.6.5 步骤5:发送端口信息
从服务器执行 REPLCONF listening-port
主服务器接收后记录在从服务器对应的客户端状态的 slave_listening_port 中,其唯一作用就是主服务器执行 INFO replication 时打印从服务器端口
15.6.6 步骤6:同步
从服务器向主服务器发送 PSYNC 命令,执行同步操作,并将自己的数据库更新至主服务器数据库当前所处的状态,执行之后主服务器也会成为从服务器的客户端
- 如果是完整重同步,将保存在缓冲区里面的写命令发送给从服务器执行
- 如果是部分重同步,发送保存在复制缓冲区
15.6.7 步骤7:命令传播
15.7 心跳检测
命令传播阶段,从服务器默认以每秒一次频率向服务器发送命令:
REPLCONF ACK <replication_offset>
replication_offset:从服务器当前的复制偏移量
三个作用:
- 检测主从服务器的网络连接状态
- 辅助实现 min-slaves 选项
- 检测命令丢失
15.7.1 检测主从服务器的网络连接状态
通过接受命令来检查网络连接是否正常,如果没有收到则连接出现问题
15.7.2 辅助实现 min-slaves 配置选项
min-slaves-to-write 和 min-slaves-max-lag 可以防止主服务器在不安全的情况下执行写命令
15.7.3 检测命令丢失
根据两个 REPLCONF ACK 命令可以得出从服务器的写命令是否在半路丢失,从复制偏移量的比较得出