Redis 主从复制

 

主从复制

Redis 的复制功能分为同步和命令传播两个操作:

  • 同步操作用于将从服务器的数据库状态更新至主服务器当前所处的数据库状态;
  • 命令传播则用于当主服务器的数据库状态被修改(数据写),导致主从服务器的数据库状态不一致时,让主从服务器的数据库重新回到一致状态。

复制

在 Redis 中,从服务器对主服务器的数据同步可以分为以下两种情况:

  • 初次复制:从服务器没有同步过主服务器数据,或者从服务器要同步的主服务器与上一次同步的主服务器不同(run ID,主服务器中有一个 run ID 属性,每次主服务器重启后,run ID 都不同);
  • 断线后重复制:处于命令传播阶段的主从服务器因为网络原因断开重连后,从服务器同步主服务器上的数据;

在 2.8 版本之前,Redis 从服务器(Slave)无论是初次复制还是断线后重复制,采取的都是全量复制策略。对于初次复制来说,这没有什么问题。但是断线后重连的 Redis 从服务器(Slave)中已经有了 Redis 主服务器(Master)数据库中的大部分数据,此时,全量复制虽然也能让主从服务器保持数据一致性,但效率却非常低。

Redis 在 2.8 版本之前的数据同步过程,Slave 服务器要同步 Master 服务器的数据时,向 Master 服务器发送 SYNC 命令。以下是 SYNC 命令的执行步骤:

  1. Slave 服务器向 Master 服务器发送 SYNC 命令;
  2. Master 服务器收到 SYNC 命令后,执行 BGSAVE 指令,创建一个子进程在后台生成一个 RDB 文件,并用一个缓冲区记录从现在开始执行的所有写命令;
  3. 当 Master 服务器执行完 BGSAVE 指令后,会将生成的 RDB 文件发送给 Slave 服务器。Slave 服务器接收到 RDB 文件后,会载入这个 RDB 文件,将自己的数据库数据更新至 Master 服务器执行 BGSAVE 命令的时候;
  4. Master 服务器将记录在缓冲区中的所有写命令发送给 Slave 服务器,Slave 服务器执行这些写命令,将自己的数据与 Master 服务器中的数据保持一致。

为了解决 Redis 断线重连后复制低效的问题,从 2.8 版本开始,Redis 使用 PSYNC 命令代替 SYNC 命令来执行复制时的同步操作,PSYNC 具有全量同步和增量同步两种模式。从服务器(Slave)断开后重连采取增量复制策略。增量复制的实现依赖于 Redis 中的复制偏移量(replication offset)、复制积压缓冲区(replication backlog)和服务器的运行ID(run ID)。

全量复制

全量复制,就是 Master 服务器会将自己数据库中的所有数据异步生成一份 RDB 文件,在文件生成后,再将这份文件发送给 Slave 服务器,Slave 服务器会解析并载入同步过来的 RDB 文件。在这个过程中,Master 服务器会缓存这段时间中执行的写命令,然后发送给 Slave 服务器,Slave 服务器执行完这部分写命令后,数据将和 Master 服务器一致。

增量复制

增量复制,是 Redis 2.8 才开始支持的功能。它是当 Redis 的 Slave 节点因网络等原因断线重连后,只从 Master 服务器中同步自己断线期间缺失的这部分数据。增量复制的实现依赖于复制偏移量、复制积压缓冲区和运行时 ID,且 Slave 节点断线重连后,也不一定会采取增量复制,还取决于它保存的 Master 的运行时 ID 以及 Master 服务器中的复制积压缓冲区。

增量复制的实现

 Redis 2.8 之后的增量复制功能的实现依赖于下面三个部分:

  • 主服务器的复制偏移量(replication offset)和从服务器的复制偏移量;
  • 主服务器的复制积压缓冲区(replication backlog);
  • 服务器的运行 ID(run ID);

复制偏移量

Redis 的主从服务器在数据同步时,都会分别维护一个复制偏移量(replication offset)

  • Master 服务器每次向 Slave 服务器传送 N 个字节的数据时,就会将自己的复制偏移量的值加上 N;
  • Slave 服务器每次收到 Master 服务器发送的 N 个字节的数据时,就会将自己的复制偏移量的值加上 N;

通过对比主从服务器的复制偏移量,程序可以很容易的知道主从服务器是否处于一致性状态。如果主从服务器的 replication offset 值相等,则主从服务器的数据处于一致性状态;如果不等,则两者数据不一致。

复制积压缓冲区

复制积压缓冲区(replication backlog)是 Master 服务器维护的一个长度固定的先进先出队列,默认大小为 1MB。当队列已满时,再向队列中添加数据,队列会移除它里面最早入队的数据。

当 Master 服务器进行命令传播时,不仅会将写命令发送给所有的 Slave 服务器,还会将写命令入队到 replication backlog 里面。因此,Master 服务器的 replication backlog 中总是保存着一定量的最近传播的写命令,并且 replication backlog 还会队列中的每个字节记录相应的复制偏移量。

当 Slave 服务器断线重连到 Master 服务器上时,Slave 服务器会将自己的复制偏移量(replication offset)和记录的主服务器的 run ID 通过 PSYNC 命令发送给 Master 服务器,Master 会根据这个偏移量来决定对 Slave 服务器执行何种同步操作:

  • 如果 offset 偏移量之后的数据,仍然存在与复制积压缓冲区中,那么 Master 服务器将对 Slave 服务器执行增量复制操作,Master 服务器会将 replication backlog 中 offset 之后的写命令发送给 Slave 服务器,完成增量同步;
  • 如果 offset 偏移量之后的数据已经不在 Master 服务器的复制积压缓冲区(也就是 Slave 服务器断线期间,Master 服务器中发生了大量写命令,导致缓冲区中队列已被写满)中,Master 服务器将对 Slave 服务器执行全量复制操作;

 Redis 中复制积压缓冲区的大小默认为 1MB,可根据实际情况(每秒产生的写命令数据量和 Slave 服务器断线重连所需的时间)评估缓冲区实际所需的大小,然后通过 redis.conf 文件中的 replrepl-backlog-size 选项来配置。

服务器运行 ID

除了复制偏移量和复制积压缓冲区外,实现部分同步功能还依赖服务器的运行 ID:

  • 每个 Redis 服务器,无论主从,都有自己的运行 ID;
  • 运行 ID 是服务器启动时自动生成的,由 40 个随机的 16 进制字符组成;

当从服务器(Slave)连接到主服务器(Master)进行初次复制时,Master 服务器会将自己的 run ID 传递给 Slave 服务器,而 Slave 服务器会将这个 run ID 保存起来。

当 Slave 服务器断线重连后,将会向当前连接的 Master 服务器发送之前保存的 run ID:

  • 如果 Slave 服务器中保存的 run ID 和它当前连接的 Master 服务器的 run ID 相同时,说明 Slave 服务器断线前后连接的是同一个主服务器,此时 Master 服务器会选择进行增量同步;
  • 相反,如果 Slave 服务器保存的 run ID 和它当前连接的 Master 服务器的 run ID 并不相同,说明在 Slave 服务器断线之间,Master 服务器也发生了变化,此时 Master 服务器将会对 Slave 服务器执行全量同步操作;

举个例子:

如果 Slave 服务器原本连接的 Master 服务器的 run ID 为:05b89ae53c5e3d4f8efb2feaa9623684b6c9ce2b。那么在 Slave 服务器因网络等原因断开重连后,Slave 服务器向 Master 服务器发送这个 run ID,Master 服务器会用自己的运行 ID 与 Slave 发送的这个 run ID 进行比较,如果相等,则根据复制积压缓冲区(replication backlog)和偏移量(replication offset)判断是否执行部分同步;如果不等,就执行全量同步。

PSYNC 命令的实现

在 Redis 2.8 版本及之后,Redis 在数据同步时用 PSYNC 命令代替了 SYNC 命令,PSYNC 命令支持全量复制和增量复制两种复制方式。

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

  • 如果 Slave 服务器之前没有复制过 Master 服务器,那么 Slave 服务器在第一次复制时会向 Master 服务器发送 PSYNC  ?  -1 命令,主动请求 Master 服务器进行全量复制;
  • 如果 Slave 服务器已经复制过 Master 服务器上的数据,那么 Slave 服务器在复制时会发送 PSYNC  <runID>  <offset> 命令,其中 runID 是 Slave 服务器记录的 Master 服务器的运行时 ID,offset 是 Slave 服务器的复制偏移量。Master 服务器接收到这两个参数后,会判断是要执行全量复制还是增量复制;

接收到 PYNC 命令后,Master 服务器会向 Slave 服务器返回下面三种回复中的一种:

  • 如果 Master 服务器返回 +FULLRESYNC  <runID>  <offset> 回复,表示 Master 服务器将执行全量复制操作。其中,runID 是 Master 服务器的运行时 ID,Slave 服务器会将这个 ID 保存下来,在下一次发送 PSYNC 命令时使用;而 offset 则是 Master 服务器的复制偏移量,Slave 服务器会将这个值作为自己的复制偏移量的初始值;
  • 如果 Master 服务器返回 +CONTINUE 回复,表示 Master 服务器将执行增量同步操作。Slave 服务器只需要等待 Master 服务器将自己缺失的那部分数据发送过来即可;
  • 如果 Master 服务器返回 -ERR 回复,表示 Master 服务器的版本低于 Redis 2.8,不支持 PSYNC 命令。而 Slave 服务器会向 Master 服务器重发 SYNC 命令,执行完整同步操作。

命令传播

在同步操作执行完毕后,主从服务器中的数据库将达到一致性状态,但这种一致并不是一成不变的。每当 Master 服务器执行客户端发送过来的写命令时,数据库中的数据就会被修改,从而导致主从服务器的数据库数据状态不再一致。

举个例子:

 

为了让主从主从服务器再次回到一致性状态,Master 服务器就会将执行过的写命令发送给 Slave 服务器,让 Slave 服务器再执行一遍。这样,主从服务器的数据库数据就又一致了。Master 服务器向 Slave 服务器发送执行过的写命令的过程,就叫做命令传播。

 

如果命令传播时,Slave 服务器因网络等原因没有接收到写命令,导致 Slave 中某条数据更新,该怎么办呢?

针对这种情况,Redis 也提供了补救措施。Redis 的 Slave 服务器每隔 1s 会向 Master 服务器发送一次 “心跳”,以证明自己还活着。而 Slave 服务器发送的 “心跳” 中就包含了自己的复制偏移量 offset,Master 服务器在接收到 Slave 服务器的复制偏移量后,会与自己的复制偏移量作比较,如果发现 Slave 的 offset 与自己的 offset 不相等,则会把复制积压缓冲区中 Slave 的 offset 后的写命令重发给 Slave 服务器,以保证数据一致。

复制功能的实现

 当某个 Redis 服务器设置为另一个 Redis 服务器的从服务器后,它会自动复制主服务器中的数据,并努力保持和主服务器的数据一致性。

  1. 当 Slave 服被设置为 Master 服务器的从节点时,它会将 Master 服务器的 IP 和端口号,保存在自己的 masterhost 和 masterpost 属性中;
  2. Slave 服务器与 Master 服务器之间建立套接字连接。连接成功后,Slave 服务器会将这个连接与一个专门处理复制工作的文件事件处理器关联,这个处理器将负责处理后续的复制工作,比如接收 RDB 文件以及在命令传播时接收 Master 服务器发送的写命令;Master 服务器会为该连接创建相应的客户端状态,并将该 Slave 服务器看做是一个连接到 Master 服务器的客户端,Slave 服务器可以向 Master 服务器发送命令请求;
  3. 发送 PING 命令。Slave 服务器成为 Master 服务器的客户端后,做的第一件事就是向 Master 服务器发送一个 PING 命令,以检查连接的读写功能是否正常以及 Master 服务器是否能正常处理 Slave 服务器发送的命令请求;
  4. 身份验证。如果 Master 服务器设置了登录密码,那么 Slave 服务器需要配置 masterauth 选项,以保证 Slave 服务器能正确连接到 Master 服务器,否则会抛出一个 no password is set 错误;
  5. 发送端口信息。身份验证后,Slave 服务器会向 Master 服务器发送一条命令:REPLCONF listening-port <port-number>,向 Master 服务器报告自己的监听端口号。Master 服务器在接收到这个命令之后,会将端口号记录在 Slave 服务器所对应的客户端状态的 slave_listening_port 属性中;
  6. 同步。Slave 服务器向 Master 服务器发送 PSYNC 命令,执行同步操作,将自己的数据库更新至 Master 服务器的数据库当前所处的状态。在同步操作执行之前,只有 Slave 服务器是 Master 服务器的客户端,但是在执行同步操作后,Master 服务器也会成为 Slave 服务器的客户端,它们之间可以互相发送命令请求以及处理对方的命令请求;
  7. 命令传播。完成同步之后,主从服务器就进入了命令传播阶段,这时 Master 服务器会一直将自己执行的写命令发送给 Slave 服务器,而 Slave 服务器会一直接受并执行 Master 发送过来的写命令。

心跳检测

 在命令传播阶段,Slave 服务器会维护一个 “心跳”,以每秒一次的频率,向 Master 服务器发送一条命令:

REPLCONF ACK <replication_offset>

其中,replication_offset 是 Slave 服务器中当前的复制偏移量。

Slave 服务器向 Master 服务器发送 “心跳” 有三个作用:

  • 检测主从服务器的网络连接状态;
  • 辅助实现 min-slaves 选项;
  • 检测命令丢失;

检测主从服务器的网络连接状态

主从服务器之间可以通过发送和接收 REPLCONF ACK 命令来检查两者之间的网络连接是否正常:如果 Master 服务器超过 1s 没有收到 Slave 服务器发送过来的 “心跳”,那么 Master 服务器就会认为它们之间的连接出现问题了。

通过向 Master 服务器发送 INFO replication 命令,可以列出 Master 服务器的 Slave 节点信息。在 Slave 的节点信息中有一个 lag 属性,记录了 Slave 服务器最后一次向 Master 服务器发送 “心跳” 的时间距离现在有多少秒。正常情况下,这个属性值应该在 0s 和 1s 之间跳动,如果超过了 1s 的话,说明主从服务器之间的连接出现了问题。

辅助实现 min-slaves 选项

Redis 的 min-slaves-tto-write 和 min-slaves-max-lag 属性可以确保 Master 服务器不会在不安全的情况下执行写命令。

举个例子,如果我们在 Master 服务器的配置文件中作了以下配置:

min-slaves-to-write 3
min-slaves-max-lag 10

那么当 Slave 服务器的数量少于 3 个,或者这三个 Slave 服务器的延迟(lag)值都大于或等于 10s 时,Master 服务器将拒绝执行写命令。这里的延迟(lag)值就是上面 INFO replication 命令返回信息中的 lag 属性。

检测命令丢失

如果因为网络等原因,导致 Master 服务器传播给 Slave 服务器的写命令在半路丢失,那么当 Slave 服务器下次向 Master 服务器发送 REPLCONF ACK 命令时,Master 服务器会检测出 Slave 服务器中的复制偏移量(replication offset)小于自己的复制偏移量值,然后 Master 服务器就判定 Slave 服务器存在命令丢失的情况,就会根据 Slave 服务器发送的 replication offset,从自己的复制积压缓冲区(replication backlog)中找到 Slave 服务器缺少的数据,并将这些数据重新发送给 Slave 服务器。

举个例子:

  1. 开始时,有一组 Redis 主从服务器的数据处于一致性状态,且复制偏移量(replication offset)都为 300;
  2. 然后一个 Redis 客户端向 Master 服务器发送了一条写命令,Master 服务器执行完写命令后,replication offset 值变为了 333;
  3. Master 服务器向 Redis 服务器传播这条写命令,但由于网络故障等原因,这条命令在传播图中丢失。这时候 Master 服务器的 replication offset 为 333,Slave 服务器的 replication offset 为 300;
  4. Slave 服务器向 Master 服务器发送 “心跳”,Master 服务器接收到 Slave 的 “心跳”后,用 Slave 发送过来的 replication offset 与自己的 replication offset 进行比较。如果发现 Slave 的 replication offset 比自己的少,说明存在命令丢失的情况,则将自己的复制积压缓冲区(replication backlog)中的 Slave 缺失(Slave 复制偏移量之后的数据)的数据重发一次;

 

在 Redis 2.8 之前的版本中,如果命令传播阶段,Master 服务器发送给 Slave 服务器的写命令丢失了,它们两个都不会注意到这个情况,Master 服务器也不会补发缺失的数据。所以为了保证主从复制时的数据一致性,最好使用 2.8 及以上版本。

 

posted on 2020-03-08 22:04  巨蟹木薯  阅读(15)  评论(0)    收藏  举报