在Redis中,可以通过slaveof
命令或者设置slaveof选项实现两台Redis服务器的主从复制,比如我们有两个Redis机器,地址分别是 127.0.0.1:6379 和 127.0.0.1:6380,现在我们在前者上面执行:
127.0.0.1:6379 > SLAVEOF 127.0.0.1:6380
那么,127.0.0.1:6379就会成为从服务器,127.0.0.1:6380就是主服务器,主从服务器通过复制会保存相同的数据,这就是数据库状态一致。今天我们探讨的重点是,主从服务器之间是如何实现数据复制的,以及slaveof
这个命令的实现原理。
1.全量复制功能的实现
主从复制的实现是通过两个操作来实现的,分别是同步(sync)和命令传播(propagate),我们看着这两个操作代表什么含义:
- 同步:将从服务器的状态更新至主服务器当前所处的数据库状态;这里会通过一个SYNC命令来完成,具体如下:
- 从服务器发送SYNC命令给主服务器;
- 主服务器接收到命令,执行
bgsave
命令,创建RDB文件; - 主服务器记录
bgsave
命令执行期间处理的客户端新命令,并写入到某个缓冲区中; - RDB文件创建完成,主服务器发送给从服务器,从服务器完成RDB文件的载入;
- 主服务器将命令缓冲区的内容发给从服务器,从服务器执行所有命令;
- 从服务器状态与主服务完成数据库状态一致。
- 命令广播:主服务器会将自己执行的写命令,发送给从服务器执行,使得两者再次保持状态一致。
2.增量复制功能的实现
主从复制分为初始化复制和断线后复制,即从服务器初始启动时,执行saveof
命令会执行一次同步,还有从服务器断线后再次链接,也会执行一次同步。
在早起的Redis的版本中,无论是首次启动还是断线后重连,都是适用SYNC
命令实现,即:全量复制,但是SYNC
是一个特别耗费资源的操作,会占用大量CPU、内存、网络和磁盘I/O的资源,所以在后期的版本中,是使用增量复制PSYNC
来实现复制操作的。
PSYNC
这个命令是同时具有完整同步和部分重同步的功能,其中完整同步的功能和SYNC
命令执行的步骤一样,而部分重同步的功能是在服务器断线重连后,如果条件允许,主服务器将断线期间的命令发送给从服务器执行,达到状态的同步的目的。所以这种部分重同步的操作相对于完整同步,是能减少很多资源消耗的。
2.1 部分重同步的实现细节
部分重同步的功能是通过3个部分构成的,分别是主从服务器两者的复制偏移量,主服务器的复制积压缓冲区,服务器的运行ID。
2.1.1 复制偏移量
复制的双方,分别会维护一个复制的偏移量:
- 主服务器的复制积压缓冲区每次向从服务器传播N个字节时,就会将自己的复制偏移量 +N;
- 从服务器在接收到N个字节数据时,会将自己的复制偏移量 +N。
这样复制的双方就可以通过复制偏移量,达到同步的目的。如果主从服务器的状态一致,那么他们的复制偏移量总是相同的,否在是处于状态不一致的情况。
2.1.2 复制积压缓冲区
复制积压缓冲区是由主服务器维护的一个固定长度先进先出的队列,默认大小是1MB。当服务器向从服务器传播命令时,它还会将此命令入队到复制积压缓冲区里面。同时,复制积压缓冲区会为入队的每一个字节
记录相应的复制偏移量值,这里的偏移量和2.1.1维护的偏移量值是相匹配。同时由于固定队列先进先出的特性,使得复制积压缓冲区中,仅保存最近一段时间执行的同步命令。
当服务器连接到主服务器时,从服务器向主服务器发送PSYNC
命令是会带上自己的复制偏移量offset,主服务器根据此偏移量决定执行哪种操作:
- 如果从服务器偏移量offset之后的数据还保存在主服务器的复制积压缓冲区里面,那么主服务器会执行部分重同步的操作。
- 如果从服务器偏移量offset之后的数据已经不在主服务器的复制积压缓冲区里面了,那么主服务器会执行完整同步的操作。
所谓的部分重同步操作,是指主服务器将从服务器偏移量offset之后的所有命令发给从服务器,避免全部命令重新发送的问题。
2.1.3 服务器运行ID
每个Redis服务器(包括主从),都会有自己的运行ID,主从服务器首次进行同步时,主服务器会将自己的运行ID发送给从服务器,从服务器会保存此ID。
当从服务器断线重连后,想要执行复制操作,会将前面保存的服务器ID发送给主服务器,此时由主服务器执行判断:
- 如果从服务器保存的此ID和自己的ID相同,那说明断线前后的主服务器是同一个,此时就会根据偏移量判断是执行全部同步还是部分重同步。
- 如果从服务器保存的此ID和自己的ID不同,那说明断线前后的主服务器已经变了,此时就会执行完整同步的操作。
3.心跳检测
在主从复制的命令传播期间,从服务器会以每秒一次的频率,向主服务器发送命令:
REPLCONF ACK <replication_offset>
其中,replication_offset是服务器当前的复制偏移量。那么发送此命令的作用是什么呢,主要是下面三个:
- 检测主从服务器之间的网络连接状态: 如果主服务器在超过1s内未收到从服务器发送的命令,就会认为两者之间的网络连接出现问题了。
- 辅助实现min-slaves选项: 在配置文件中,有这么两个参数,min-replicas-to-write 和min-replicas-max-lag,如下,如果我们开启了此选项,表示如果从服务器数量少于3个,或者三个主从服务器之间的复制延迟都大于等于10s时,主服务器将拒绝执行写命令,这里主要是为了防止主服务器在不安全的情况下执行写命令。
# 从服务器的数量是3个
min-replicas-to-write 3
# 主从服务器之间的延迟时间,单位是3
min-replicas-max-lag 10
- 检测广播命令是否丢失: 在命令广播期间,因为网路问题可能存在命令在半路丢失的情况,所以通过此命令的 replication_offset,即从服务器的复制偏移量,主服务器就可以获悉从服务器是否成功执行了上一次发送的命令。如果主从的复制偏移量相等,说明命令传播没有问题,如果不相等,说明命令有丢失或从服务器执行失败的情况,此时主服务器就会把从服务器偏移量之后的命令从新发送给从服务器执行,保证两个服务器状态的一致性。