1、简介
主从复制是 Redis 分布式的基石,也是 Redis 高可用的保障。在 Redis 中,被复制的服务器称为主服务器(Master),对主服务器进行复制的服务器称为从服务器(Slave)。
主从复制的配置非常简单,有三种方式(其中 IP-主服务器 IP 地址/PORT-主服务器 Redis 服务端口):
-
配置文件——redis.conf 文件中,配置 slaveof ip port
-
命令——进入 Redis 客户端执行 slaveof ip port
-
启动参数—— ./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 命令来完成:
-
从服务器接收到客户端发送的 slaveof ip prot 命令,从服务器根据 ip:port 向主服务器创建套接字连接
-
套接字成功连接到主服务器后,从服务器会为这个套接字连接关联一个专门用于处理复制工作的文件事件处理器,处理后续的主服务器发送的 RDB 文件和传播的命令
-
开始进行复制,从服务器向主服务器发送 sync 命令
-
主服务器接收到 sync 命令后,执行 bgsave 命令,主服务器主进程 fork 的子进程会生成一个 RDB 文件,同时将 RDB 快照产生后的所有写操作记录在缓冲区中
-
bgsave 命令执行完成后,主服务器将生成的 RDB 文件发送给从服务器,从服务器接收到 RDB 文件后,首先会清除本身的全部数据,然后载入 RDB 文件,将自己的数据状态更新成主服务器的 RDB 文件的数据状态
-
主服务器将缓冲区的写命令发送给从服务器,从服务器接收命令,并执行。
-
主从复制同步步骤完成
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。