Redis 主从复制原理

39 阅读5分钟

第一次同步

服务器A和服务器B,在服务器B执行命令:

replicaof <A服务器IP> <A服务器Redis端口>

B服务器就变成A服务器的从服务器。

主从服务器件的第一次同步的三个阶段:

  • 第一阶段是建立连接、协商同步
  • 第二阶段是主服务器同步数据给从服务器
  • 第三阶段是主服务器发送新的写操作命令给从服务器
第一阶段
  • 从服务器执行replicaof命令后,从服务器会给主服务器发送一个命令psync表示进行数据同步。
  • psync 包含两个参数 psync runID offset
    • runID:每个redis服务器在启动时都会自动生成一个随机且唯一的ID了标识自己。当从服务器与主服务器第一次同步的时不知道从服务器的runID,所以将其设置为? replicaof ? -1
    • offset:表示复制的进度,第一次同步的时候传-1。
  • 当主服务器接收到从服务器的psync命令之后,会使用FULLRESYNC回应从服务器。
    • 响应为FULLRESYNC runID offset。这里的runID为主服务器ID,offset复制进度
    • FULLRESYNC响应命令意思就是要以全量复制的方式,就需要把主服务器的数据全部同步到从服务器上
第二阶段
  • 主服务器执行bgsave命令生成RDB快照,然后将文件发送到从服务器。
    • 执行bgsave命名不会使主进程阻塞,因为是子进程完成的RDB快照
    • 这个时候如果主进程有写命令操作,RDB快照是不会记录的,主从服务器数据会不一致。
  • 同步期间如果主服务器有写命令操作,会记录在replication buffer缓冲区中。
    • 主服务器生成RDB文件
    • 主服务器发送RDB文件
    • 从服务器执行RDB文件
  • 注意:从服务器在接收到主服务器发送过来的RDB快照的时候,会先讲自己的数据全部清空,然后在执行RDB快照。
第三阶段
  • 当主服务器将RDB文件发送到从服务器上,从服务器接收到RDB文件之后会将旧数据丢弃,然后执行RDB文件,执行完成之后会回复主服务器,发送一个确认消息。
  • 主服务器收到确认消息之后会将replication buffer缓冲区中的写操作命令记录发送给从服务器,从服务器执行主服务器的replication buffer的命令,使得主从服务器数据一致。
增量复制

在完成第一次同步之后,就会基于长连接(tcp)进行命令的传播。

当主服务器给从服务器发送命令的时候出现网络连接断开的情况下是如何处理的呢?

  • 当前连接断之后,然后又恢复
  • 从服务器恢复连接之后发送命令 psync命令给主服务器,这个时候的offset不再是-1
  • 主服务器收到命令之后,然后用CONTINUE响应命令告知从服务器,接下来采用增量复制的方式同步数据
  • 然后主服务器将从开始断开连接期间,所执行的写命令发送给从服务器,然后从服务器执行这些命令

主服务器怎么知道将哪些增量的数据发送给从服务器呢?

  • repl_backlog_buffer:环形缓冲区,用于主从服务器断开后,从中找出差异
  • replcation offset:标记上面那个缓冲区的同步进度,主从服务器都有各自的偏移量
    • 主服务器使用master_repl_offset记录自己写到的位置
    • 从服务器使用slave_repl_offset来记录自己读到的位置

主服务器何时开始将记录写入repl_backlog_buffer缓冲区中呢?

  • 在主服务器进行命令传播的时候,不仅将写命令发送给从服务器,还会写入repl_backlog_buffer缓冲区中,所以会保留最近的传播的写命令。

当从服务器恢复连接之后,通过发送命令psync runID offset命令将自己的复制偏移量slave_repl_offset告知主服务器,主服务器根据自己的master_repl_offset和从服务器发送过来的salve_repl_offset之间的差距,来觉得对从服务器使用哪种同步:

  • 如果判断出从服务器需要读取的数据还在repl_backlog_buffer缓冲区里,那么主服务器将采用增量复制的方式
    • 这里会将差异区间的数据写入replication buffer缓冲区中,这个缓冲区是主库保存即将发给从库命令的缓冲区。
  • 如果判断出从服务器需要读取的数据不在repl_backlog_buffer缓冲区里,那么主服务器将采用全量复制方式

repl_backlog_buffer缓冲区大小为1M并且是环形的一个缓冲区,缓冲区写满就会被覆盖。如果主服务器的速度远超从服务器的速度,缓冲区的数据早就被覆盖了。
如果这个时候网络恢复了,从服务器想读取的数据已经被覆盖了,主服务器只能采用全量的方式同步给从服务器。

全量复制的代价还是挺大的,性能上的消耗等等,如何解决?

  • 可以设置repl_backlog_buffer的大小设置大一些
  • 公式second * write_size_pre_second
    • second:为从服务器断开后重新连接上主服务器所需要的平均时间(单位:秒)
    • write_size_pre_second:主服务器平均每秒产生的写命令数据量大小 例如:主服务器平均每秒产生的写命令数据量大小为2M,而从服务器断开连接后重新连接上主服务器的平均时间为10s,这个时候repl_backlog_buffer大小就不能小于20M,否则会出被覆盖的然后全量复制的风险。为了预防位置风险,尽量设置在计算基础上的2倍,以刚刚的例子就是(20M)。

在配置文件中可以修改repl-locklog-size