概述

主从复制是指将主节点(master)的数据复制到从节点(slave)中,数据的复制是单向的,只能由主节点到从节点。

基本原理

主从复制过程大体可以分为3个阶段:连接建立阶段、数据同步阶段、命令传播阶段。

连接建立阶段

主从复制的开启,完全是在从节点发起的(通过slaveof命令),不需要我们在主节点做任何事情。需要注意的是,slaveof是异步命令,从节点完成主节点ip和port的保存后,向发送slaveof命令的客户端直接返回OK,实际的复制操作在这之后才开始进行。

数据同步阶段

主从节点之间的连接建立以后,便可以开始进行数据同步。数据同步阶段是主从复制最核心的阶段,由从节点向主节点发送psync命令开始同步,并且根据主从节点当前状态的不同,可以分为全量复制和部分复制。

全量复制

全量复制用于初次复制或其他无法进行部分复制的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作。执行过程如下:

  • 主节点收到全量复制的命令后,执行bgsave,在后台生成RDB文件,并使用一个缓冲区(称为复制缓冲区)记录从现在开始执行的所有写命令。
  • 主节点的bgsave执行完成后,将RDB文件发送给从节点,从节点首先清除自己的旧数据,然后载入接收的RDB文件,将数据库状态更新至主节点执行bgsave时的数据库状态。
  • 主节点将复制缓冲区中的所有写命令发送给从节点,从节点执行这些写命令,将数据库状态更新至主节点的最新状态。
  • 如果从节点开启了AOF,则会触发bgrewriteaof的执行,从而保证AOF文件更新至主节点的最新状态。

部分复制

复制偏移量:主节点和从节点分别维护一个复制偏移量(offset),代表的是主节点向从节点传递的字节数。主节点每次向从节点传播N个字节数据时,主节点的offset增加N;从节点每次收到主节点传来的N个字节数据时,从节点的offset增加N。offset用于判断主从节点的数据库状态是否一致:如果二者offset相同,则一致;如果offset不同,则不一致,此时可以根据两个offset找出从节点缺少的那部分数据。

复制缓冲区:复制缓冲区是由主节点维护的、固定长度的、先进先出队列,默认大小1MB,当主节点开始有从节点时创建。在命令传播阶段,主节点除了将写命令发送给从节点,还会发送一份给复制缓冲区,作为写命令的备份。除了存储写命令,复制缓冲区中还存储了其中的每个字节对应的复制偏移量(offset)。由于复制缓冲区定长且是先进先出,所以它保存的是主节点最近执行的写命令,时间较早的写命令会被挤出缓冲区,因此当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。

服务器运行ID(runid):每个Redis节点(无论主从),在启动时都会自动生成一个随机ID(每次启动都不一样),runid用来唯一识别一个Redis节点。主从节点初次复制时,主节点将自己的runid发送给从节点,从节点将这个runid保存起来。当断线重连时,从节点会将这个runid发送给主节点;主节点根据runid判断能否进行部分复制:

  • 如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况);
  • 如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,只能进行全量复制。

命令传播阶段

数据同步阶段完成后,主从节点进入命令传播阶段。在这个阶段主节点将自己执行的写命令发送给从节点,从节点接收命令并执行,从而保证主从节点数据的一致性。需要注意的是,命令传播是异步的过程,即主节点发送写命令后并不会等待从节点的回复,因此实际上主从节点之间很难保持实时的一致性,延迟在所难免。

故障转移

在没有使用哨兵或集群的场景下,当主节点挂掉了以后,从节点没法完成master选举,从而导致整个服务对外不可用。我们需要自行选择一个从节点作为主节点并继续对外提供服务,并将其它从节点的主节点设为它,由于这些过程都需要手动完成,出错率比较高,因此在真实场景中往往都会使用哨兵或者集群来实现系统的高可用。

参考资料