写点什么

Redis 之主从复制详述,看完这篇文章不再稀里糊涂

作者:李子捌
  • 2021 年 11 月 27 日
  • 本文字数:4048 字

    阅读完需:约 13 分钟

Redis之主从复制详述,看完这篇文章不再稀里糊涂

1、简介

主从复制是 Redis 分布式的基石,也是 Redis 高可用的保障。在 Redis 中,被复制的服务器称为主服务器(Master),对主服务器进行复制的服务器称为从服务器(Slave)。



主从复制的配置非常简单,有三种方式(其中 IP-主服务器 IP 地址/PORT-主服务器 Redis 服务端口):

  1. 配置文件——redis.conf 文件中,配置 slaveof ip port

  2. 命令——进入 Redis 客户端执行 slaveof ip port

  3. 启动参数—— ./redis-server --slaveof ip port


2、主从复制的演进

Redis 的主从复制机制,并不是一开始就像 6.x 版本一样完善,而是一个版本一个版本迭代而来的。它大体上经过三个版本的迭代:

  • 2.8 以前

  • 2.8~4.0

  • 4.0 以后

随着版本的增长,Redis 主从复制机制逐渐完善;但是他们的本质都是围绕同步(sync)和命令传播(command propagate)两个操作展开:

  • 同步(sync):指的是将从服务器的数据状态更新至主服务器当前的数据状态,主要发生在初始化或后续的全量同步。

  • 命令传播(command propagate):当主服务器的数据状态被修改(写/删除等),主从之间的数据状态不一致时,主服务将发生数据改变的命令传播给从服务器,让主从服务器之间的状态重回一致。


2.1 版本 2.8 以前

2.1.1 同步

2.8 以前的版本,从服务器对主服务器的同步需要从服务器向主服务器发生 sync 命令来完成:



  1. 从服务器接收到客户端发送的 slaveof ip prot 命令,从服务器根据 ip:port 向主服务器创建套接字连接

  2. 套接字成功连接到主服务器后,从服务器会为这个套接字连接关联一个专门用于处理复制工作的文件事件处理器,处理后续的主服务器发送的 RDB 文件和传播的命令

  3. 开始进行复制,从服务器向主服务器发送 sync 命令

  4. 主服务器接收到 sync 命令后,执行 bgsave 命令,主服务器主进程 fork 的子进程会生成一个 RDB 文件,同时将 RDB 快照产生后的所有写操作记录在缓冲区中

  5. bgsave 命令执行完成后,主服务器将生成的 RDB 文件发送给从服务器,从服务器接收到 RDB 文件后,首先会清除本身的全部数据,然后载入 RDB 文件,将自己的数据状态更新成主服务器的 RDB 文件的数据状态

  6. 主服务器将缓冲区的写命令发送给从服务器,从服务器接收命令,并执行。

  7. 主从复制同步步骤完成


2.1.2 命令传播

当同步工作完成之后,主从之间需要通过命令传播来维持数据状态的一致性。

如下图,当前主从服务器之间完成同步工作之后,主服务接收客户端的 DEL K6 指令后删除了 K6,此时从服务器仍然存在 K6,主从数据状态并不一致。为了维持主从服务器状态一致,主服务器会将导致自己数据状态发生改变的命令传播到从服务器执行,当从服务器也执行了相同的命令之后,主从服务器之间的数据状态将会保持一致。



2.1.3 缺陷

从上面看不出 2.8 以前版本的主从复制有什么缺陷,这是因为我们还没有考虑网络波动的情况。了解分布式的兄弟们肯定听说过 CAP 理论,CAP 理论是分布式存储系统的基石,在 CAP 理论中 P(partition 网络分区)必然存在,Redis 主从复制也不例外。当主从服务器之间出现网络故障,导致一段时间内从服务器与主服务器之间无法通信,当从服务器重新连接上主服务器时,如果主服务器在这段时间内数据状态发生了改变,那么主从服务器之间将出现数据状态不一致。

在 Redis 2.8 以前的主从复制版本中,解决这种数据状态不一致的方式是通过重新发送 sync 命令来实现。虽然 sync 能保证主从服务器数据状态一致,但是很明显 sync 是一个非常消耗资源的操作。


sync 命令执行,主从服务器需要占用的资源:

  • 主服务器执行 BGSAVE 生成 RDB 文件,会占用大量 CPU、磁盘 I/O 和内存资源

  • 主服务器将生成的 RDB 文件发送给从服务器,会占用大量网络带宽,

  • 从服务器接收 RDB 文件并载入,会导致从服务器阻塞,无法提供服务

从上面三点可以看出,sync 命令不仅会导致主服务器的响应能力下降,也会导致从服务器在此期间拒绝对外提供服务。


2.2 版本 2.8-4.0

2.2.1 改进点

针对 2.8 以前的版本,Redis 在 2.8 之后对从服务器重连后的数据状态同步进行了改进。改进的方向是减少全量同步(full resynchronizaztion)的发生,尽可能使用增量同步(partial resynchronization)。在 2.8 版本之后使用 psync 命令代替了 sync 命令来执行同步操作,psync 命令同时具备全量同步和增量同步的功能:

  • 全量同步与上一版本(sync)一致

  • 增量同步中对于断线重连后的复制,会根据情况采取不同措施;如果条件允许,仍然只发送从服务缺失的部分数据。

2.2.2 psync 如何实现

Redis 为了实现从服务器断线重连后的增量同步,增加了三个辅助参数:

  • 复制偏移量(replication offset)

  • 积压缓冲区(replication backlog)

  • 服务器运行 id(run id)

2.2.2.1 复制偏移量

在主服务器和从服务器内都会维护一个复制偏移量

  • 主服务器向从服务发送数据,传播 N 个字节的数据,主服务的复制偏移量增加 N

  • 从服务器接收主服务器发送的数据,接收 N 个字节的数据,从服务器的复制偏移量增加 N

正常同步的情况如下:



通过对比主从服务器之间的复制偏移量是否相等,能够得知主从服务器之间的数据状态是否保持一致。

假设此时 A/B 正常传播,C 从服务器断线,那么将出现如下情况:



很明显有了复制偏移量之后,从服务器 C 断线重连后,主服务器只需要发送从服务器缺少的 100 字节数据即可。但是主服务器又是如何知道从服务器缺少的是那些数据呢?


2.2.2.2 复制积压缓冲区

复制积压缓冲区是一个固定长度的队列,默认为 1MB 大小。当主服务器数据状态发生改变,主服务器将数据同步给从服务器的同时会另存一份到复制积压缓冲区中。



复制积压缓冲区为了能和偏移量进行匹配,它不仅存储了数据内容,还记录了每个字节对应的偏移量:



当从服务器断线重连后,从服务器通过 psync 命令将自己的复制偏移量(offset)发送给主服务器,主服务器便可通过这个偏移量来判断进行增量传播还是全量同步。

  • 如果偏移量 offset+1 的数据仍然在复制积压缓冲区中,那么进行增量同步操作

  • 反之进行全量同步操作,与 sync 一致


Redis 的复制积压缓冲区的大小默认为 1MB,如果需要自定义应该如何设置呢?

很明显,我们希望能尽可能的使用增量同步,但是又不希望缓冲区占用过多的内存空间。那么我们可以通过预估 Redis 从服务断线后重连的时间 T,Redis 主服务器每秒接收的写命令的内存大小 M,来设置复制积压缓冲区的大小 S。

S = 2 * M * T

注意这里扩大 2 倍是为了留有一定的余地,保证绝大部分的断线重连都能采用增量同步。


2.2.2.3 服务器运行 ID

看到这里是不是再想上面已经可以实现断线重连的增量同步了,还要运行 ID 干嘛?其实还有一种情况没考虑,就是当主服务器宕机后,某台从服务器被选举成为新的主服务器,这种情况我们就通过比较运行 ID 来区分。

  • 运行 ID(run id)是服务器启动时自动生成的 40 个随机的十六进制字符串,主服务和从服务器均会生成运行 ID

  • 当从服务器首次同步主服务器的数据时,主服务器会发送自己的运行 ID 给从服务器,从服务器会保存在 RDB 文件中

  • 当从服务器断线重连后,从服务器会向主服务器发送之前保存的主服务器运行 ID,如果服务器运行 ID 匹配,则证明主服务器未发生更改,可以尝试进行增量同步

  • 如果服务器运行 ID 不匹配,则进行全量同步


2.2.3 完整的 psync

完整的 psync 过程非常的复杂,在 2.8-4.0 的主从复制版本中已经做到了非常完善。psync 命令发送的参数如下:

psync <runid> <offset>

当从服务器没有复制过任何主服务器(并不是主从第一次复制,因为主服务器可能会变化,而是从服务器第一次全量同步),从服务器将会发送:

psync ? -1



一起完整的 psync 流程如下图:



  1. 从服务器接收到 SLAVEOF 127.0.0.1 6379 命令

  2. 从服务器返回 OK 给命令发起方(这里是异步操作,先返回 OK,再保存地址和端口信息)

  3. 从服务器将 IP 地址和端口信息保存到 Master Host 和 Master Port 中

  4. 从服务器根据 Master Host 和 Master Port 主动向主服务器发起套接字连接,同时从服务将会未这个套接字连接关联一个专门用于文件复制工作的文件事件处理器,用于后续的 RDB 文件复制等工作

  5. 主服务器接收到从服务器的套接字连接请求,为该请求创建对应的套接字连接之后,并将从服务器看着一个客户端(在主从复制中,主服务器和从服务器之间其实互为客户端和服务端)

  6. 套接字连接建立完成,从服务器主动向主服务发送 PING 命令,如果在指定的超时时间内主服务器返回 PONG,则证明套接字连接可用,否则断开重连

  7. 如果主服务器设置了密码(masterauth),那么从服务器向主服务器发送 AUTH masterauth 命令,进行身份验证。注意,如果从服务器发送了密码,主服务并未设置密码,此时主服务会发送 no password is set 错误;如果主服务器需要密码,而从服务器未发送密码,此时主服务器会发送 NOAUTH 错误;如果密码不匹配,主服务器会发送 invalid password 错误。

  8. 从服务器向主服务器发送 REPLCONF listening-port xxxx(xxxx 表示从服务器的端口)。主服务器接收到该命令后会将数据保存起来,当客户端使用 INFO replication 查询主从信息时能够返回数据

  9. 从服务器发送 psync 命令,此步骤请查看上图 psync 的两种情况

  10. 主服务器与从服务器之间互为客户端,进行数据的请求/响应

  11. 主服务器与从服务器之间通过心跳包机制,判断连接是否断开。从服务器每个 1 秒向主服务器发送命令,REPLCONF ACL offset(从服务器的复制偏移量),该机制可以保证主从之间数据的正确同步,如果偏移量不相等,主服务器将会采取增量/全量同步措施来保证主从之间数据状态一致(增量/全量的选择取决于,offset+1 的数据是否仍在复制积压缓冲区中)


2.3 版本 4.0

Redis 2.8-4.0 版本仍然有一些改进的空间,当主服务器切换时,是否也能进行增量同步呢?因此 Redis 4.0 版本针对这个问题做了优化处理,psync 升级为 psync2.0。

psync2.0 抛弃了服务器运行 ID,采用了 replid 和 replid2 来代替,其中 replid 存储的是当前主服务器的运行 ID,replid2 保存的是上一个主服务器运行 ID。

  • 复制偏移量(replication offset)

  • 积压缓冲区(replication backlog)

  • 主服务器运行 id(replid)

  • 上个主服务器运行 id(replid2)


通过 replid 和 replid2 我们可以解决主服务器切换时,增量同步的问题:

  • 如果 replid 等于当前主服务器的运行 id,那么判断同步方式增量/全量同步

  • 如果 replid 不相等,则判断 replid2 是否相等(是否同属于上一个主服务器的从服务器),如果相等,仍然可以选择增量/全量同步,如果不相等则只能进行全量同步。


发布于: 2021 年 11 月 27 日阅读数: 13
用户头像

李子捌

关注

华为云享专家 2020.07.20 加入

公众号【李子捌】

评论

发布
暂无评论
Redis之主从复制详述,看完这篇文章不再稀里糊涂