Redis主从复制深入剖析

在Redis中可以使用SLAVEOF命令或者配置slaveof配置项,让一个服务器复制另一个服务器,被复制的服务器为主服务器,配置slaveof的服务器为从服务器,从服务器可以再被复制,形成级联。

依据Redis2.8版本,复制机制分为旧版复制和新版复制,旧版复制因为存在主从服务器断线重连后会进行全量复制的问题,所以在2.8版本之后,redis使用新版复制机制,替换了旧版的复制机制,下面将从旧版复制机制原理、旧版复制机制的缺陷、新版复制机制原理、新版复制机制的详细步骤展开陈述。

1、旧版的主从复制原理

Redis的主从复制分为同步(sync)和命令传播(command propagate)两个操作:

  • 同步操作用是将从服务器同步主服务器当前数据库状态。

  • 命令传播作用是当主服务器的状态出现变化,导致主从服务器的状态不一致时,将导致不一致的命令传播给从服务器,使主从服务器数据库状态变成一致。

1.1、同步操作

当客户端向服务器发送SLAVEOF命令或者服务器配置slaveof配置项,这时服务器变成一个从服务器,从服务器开始复制主服务器,首先要做的就是执行同步操作。

执行同步操作需要使用SYNC命令,同步操作的具体执行步骤如下:

  • 首先从服务器作为主服务器客户端向主服务器发送SYNC命令。

  • 主服务器在收到SYNC命令是,会fork出一个子进程,执行BGSAVE命令(Redis的RDB持久化命令,具体在Redis持久化剖析中解释),生成一个RDB文件,同时在缓冲区开始记录BGSAVE执行之后执行的所有写命令。注意:只缓存写命令,缓存读命令没有意义。

  • 主服务器的BGSAVE命令执行完成后,主服务器将生层的RDB文件发送给从服务器。

  • 从服务器接收到RDB文件后,载入这个RDB文件,将自己的数据库状态同步到主服务器执行BGSAVE时的状态。

  • 主服务器将缓存记录的所有写命令发送给从服务器,从服务器执行这些写命令,将自己的数据库状态同步到主服务器当前最新状态。

1.2、命令传播

在执行完成同步操作之后,主从数据库暂时的状态一致,但此时主服务器还在继续接收写命令,任何一个写命令都会导致主从数据库状态不一致,这是就需要主服务器将写命令传播到从服务器。

命令传播就是主服务器将自己执行的命令,发送给从服务器执行,此时主服务器是从服务器的客户端,主服务器以客户端的身份将自己接收到的写命令发送给从服务器。

2、旧版主从复制机制的缺陷

简单来说,Redis2.8版本之前的主从复制,在主从服务器断线重连后的处理存在缺陷,就是断线重连后会进行全量的同步操作。

假设,主从服务器在某个时间点断开,主服务器继续接收了3个写命令,此时从服务器断开,所以这三个命令无法传播到从服务器,主从服务器状态不在一致,5分钟之后主从服务器重新连接,此时从服务器发送SYNC命令进行同步操作。

这时主服务器执行的BGSAVE,生成RDB全量文件,发送给从服务器,从服务器接收到之后加载RDB,同步状态。

这种操作可以实现主从数据库的状态一致,但是,实际上我们只需要将断线后接收到的3个写命令发送给从服务器就可以达到状态一致的要求。

注意,SYNC非常消耗时间和资源,执行BGSAVE,会消耗CPU、磁盘IO、内存,将RDB发送给从服务器会消耗主服务器大量的网络资源,甚至造成主服务器网络宕机,从服务器在载入RDB文件是会被阻塞,无法执行任何请求。

3、新版复制机制原理

新版复制PSYNC命令具有完整重同步和部分重同步两种模式:

  • 完整重同步主要用于服务器启动,初次复制的情况,执行步骤和SYNC基本一致。

  • 部分重同步用于处理断线重连的情况,基本原理就是断线重连后,主服务器会把断线期间执行的写命令发送给从服务器进行状态同步。

3.1 部分重同步实现

部分重同步的实现由三个部分构成:
1、复制偏移量:主服务器和从服务器各自维护一个复制偏移量,复制偏移量的主要作用:

  • 主服务器每次向从服务器发送N个字节的命令时,主服务器自己的复制偏移量增加N。

  • 从服务器每次接收到主服务器传播过来的N字节的命令时,从服务器自己的复制偏移量增加N。

  • 如果主从服务器状态一致,那么主从服务器的复制偏移量一定是相同的。

  • 如果主从服务器的复制偏移量不同,那么主从服务器状态一定不一致。

2、复制积压缓冲区:只存在主服务器中,从服务器没有。
主服务器维护的一个固定长度的先进先出队列,当进行命令传播时,将命令发送给从服务器的同时,还会将命令写入复制积压缓冲区里面,主服务器的复制积压缓冲区里面保存着一部分最近传播的命令,并且复制积压缓冲区会记录每个字节对应的复制偏移量。

当从服务器重新连上主服务器时,从服务器会通过PSYNC命令将自己的复制偏移量offset发送给主服务器,主服务器会根据这个复制偏移量来决定对从服务器执行何种同步操作:

  • 如果offset偏移量之后的数据(也即是偏移量offset+1开始的数据)仍然存在于复制积压缓冲区里面,那么主服务器将对从服务器执行部分重同步操作;

  • 相反,如果offset偏移量之后的数据已经不存在于复制积压缓冲区,那么主服务器将对从服务器执行完整重同步操作。

Redis为复制积压缓冲区设置的默认大小为1MB。

3、服务器运行ID:每个Redis服务器都具有唯一的ID,服务器启动时生成,由40个随机的16进制字符构成。

  • 每个Redis服务器,不论主服务器还是从服务,都会有自己的运行ID。

  • 运行ID在服务器启动时自动生成,由40个随机的十六进制字符组成,例如53b9b28df8042fdc9ab5e3fcbbbabff1d5dce2b3。

当从服务器对主服务器进行初次复制时,主服务器会将自己的运行ID传送给从服务器,而从服务器则会将这个运行ID保存起来(注意哦,是从服务器保存了主服务器的ID)。

当从服务器断线并重新连上一个主服务器时,从服务器将向当前连接的主服务器发送之前保存的运行ID:

  • 如果从服务器保存的运行ID和当前连接的主服务器的运行ID相同,那么说明从服务器断线之前复制的就是当前连接的这个主服务器,主服务器可以继续尝试执行部分重同步操作;

  • 相反地,如果从服务器保存的运行ID和当前连接的主服务器的运行ID并不相同,那么说明从服务器断线之前复制的主服务器并不是当前连接的这个主服务器,主服务器将对从服务器执行完整重同步操作。

4、PSYNC命令的实现

PSYNC命令的调用方法有两种:

  • 如果从服务器以前没有复制过任何主服务器,或者之前执行过SLAVEOF no one命令,那么从服务器在开始一次新的复制时将向主服务器发送PSYNC ? -1命令,主动请求主服务器进行完整重同步(因为这时不可能执行部分重同步);

  • 相反地,如果从服务器已经复制过某个主服务器,那么从服务器在开始一次新的复制时将向主服务器发送PSYNC 命令:其中runid是上一次复制的主服务器的运行ID,而offset则是从服务器当前的复制偏移量,接收到这个命令的主服务器会通过这两个参数来判断应该对从服务器执行哪种同步操作。

根据情况,接收到PSYNC命令的主服务器会向从服务器返回以下三种回复的其中一种:

  • 如果主服务器返回+FULLRESYNC 回复,那么表示主服务器将与从服务器执行完整重同步操作:其中runid是这个主服务器的运行ID,从服务器会将这个ID保存起来,在下一次发送PSYNC命令时使用;而offset则是主服务器当前的复制偏移量,从服务器会将这个值作为自己的初始化偏移量;

  • 如果主服务器返回+CONTINUE回复,那么表示主服务器将与从服务器执行部分重同步操作,从服务器只要等着主服务器将自己缺少的那部分数据发送过来就可以了;

  • 如果主服务器返回-ERR回复,那么表示主服务器的版本低于Redis 2.8,它识别不了PSYNC命令,从服务器将向主服务器发送SYNC命令,并与主服务器执行完整同步操作。

坚持原创技术分享,您的支持将鼓励我继续创作!