关于Redis的主从复制

776 阅读6分钟

:::tip

SLAVEOF host:port 命令可以将当前服务器去复制目标服务器,进行复制中的主从服务器的数据库将保存相同的数据,概念上称为数据库状态一致

:::

旧版复制功能

同步(sync)

当从服务器发送SLAVEOF命令要求复制主服务器时,从服务器首先执行同步操作,即将从服务器的数据库状态更新至主服务器当前所处的数据库状态,具体步骤如下

  1. 从服务器发送SYNC命令
  2. 收到命令的主服务执行BGSAVE命令,在后台生成RDB文件,并使用缓冲区记录从现在执行BGSAVE之后执行的所有写命令
  3. 主服务器执行完BGSAVE之后,主服务器会将RDB文件发送给从服务器,从服务器接收并载入这个RDB文件
  4. 主服务器将记录在缓冲区的所有写命令发送给从服务器,从服务器执行这些写命令

命令传播(command propagate)

同步操作完成后,主从服务器目前的数据库状态达到一致,但这种状态不是一成不变的,每当主服务器执行客户端发送的写命令时,主从服务器的数据库就不再一致

所以为了保持主从服务器的数据一致,主服务器需要对从服务器执行命令传播:将写命令发送给从服务器,当从服务也执行完后,主从服务器将再次回到一致状态

缺陷

旧版复制功能的缺陷主要体现在从服务器断线重连的处理

如上图的情况,从服务器在某一刻断线,再此期间主服务器增加了k3,k4两个键,按目前的实现逻辑,从服务器在之后重连后,依旧是再次发送SYNC命令进行同步(主服务器生成和发送RDB文件给从服务器载入)

但其实从服务器主需要断线期间的k3,k4键即可,RDB文件中的其他键信息对从服务器来说都是不必要的

:::tip

SYNC是一个非常耗资源的操作

  1. 主服务器需要执行BGSAVE来生成RDB文件,这耗费CPU,内存和磁盘IO资源
  2. 主服务器需要将RDB发送给从服务器,这耗费网络带宽
  3. 从服务器加载RDB文件时会阻塞其他命令的处理

:::

新版复制功能

PSYNC命令替代SYNC

为了解决断线重连后的重同步问题,Redis2.8以后使用了PSYNC代替SYNC进行同步操作,有以下两种模式:

  1. 完整重同步:用于初次复制,执行步骤与SYNC基本一致。 PSYNC ? -1
  2. 部分重同步:用于断线重连后的重复制,在条件允许的情况下,只需要将断开期间主服务器的写命令发送给从服务器 。PSYNC runid offset

以下是执行部分重同步时的通信过程

部分重同步的工作原理

部分重同步主要由三个部分组成

  1. 主服务器的复制偏移量和从服务器的复制偏移量
  2. 主服务器的复制积压缓冲区
  3. 服务器的运行ID

复制偏移量

每一个服务器都会分别维护一个复制偏移量:

  1. 主服务每次向从服务器传播N个字节的数据(写命令)时,就将自己的复制偏移量加N
  2. 从服务每次收到主服务器传播的N个字节的数据(写命令)时,就将自己的复制偏移量加N

那么通过对比主从服务器的复制偏移量就可以知道

  1. 它们目前的状态是否一致
  2. 它们之间缺失了多少数据

复制积压缓冲区

通过复制偏移量可以知道主从服务器之间缺失了多少数据,那么缺失的数据是什么?要去哪里找回来呢?

这里就用上了复制积压缓冲区了

当主服务器进行命令传播时,它不仅将写命令发送给所有从服务器,还会将写命令放进一个固定长度的先进先出的队列里,这个队列就是复制积压缓冲区(默认大小是1MB)

当从服务器重连后,从服务器通过PSYNC命令将自己的复制偏移量发送给主服务器

  1. 如果从服务器复制偏移量之后的数据还在复制积压缓冲区中,那么进行部分重同步的操作
  2. 如果从服务器复制偏移量之后的数据不在复制积压缓冲区中,那么进行完整重同步的操作

:::tip

如何调整复制积压缓冲区的大小

通过redis.conf中的repl-backlog-size进行调整

一般调整为 2 * 断线重连所需的平均秒数 * 主服务器每秒产生的写命令数量

:::

服务器运行ID

:::tip

每个Redis服务器都拥有自己的运行ID

运行ID由服务器启动时生成,由40个随机的十六进制字符组成

:::

初次复制时,从服务器会保存主服务器的运行ID

当从服务器断线重连时,从服务器将这个运行ID发送给主服务器:

  1. 如果与主服务器的运行ID相同,则说明断线前复制的就是当前连接的主服务器,主服务器可以根据条件尝试进行部分重同步
  2. 如果与主服务器的运行ID不同,则说明断线前复制的不是当前连接的主服务器,主服务器将对从服务器进行完整重同步

PSYNC执行时可能遇上的情况

从服务器执行SLAVEOF的完整过程

  1. 设置主服务器的地址和端口
  2. 与主服务器建立套接字连接
  3. 发送PING命令
  4. 身份验证
  5. 向主服务器发送从服务器端口信息 REPLCONF listening-port <port-number >
  6. 同步
  7. 命令传播

心跳检测

:::tip

在命令传播阶段,从服务器默认一秒一次的速率,向主服务器发送指令

REPLCONF ACK <replication_offset>

其中replication_offset时从服务器当前的复制偏移量

:::

发送REPLCONF ACK命令主要由三个作用:

  1. 检测主从服务器的连接状态
  2. 辅助实现min-slaves配置选项
  3. 检测命令丢失

参考

  1. Redis设计与实现