一、概述
Redis的主从复制策略是通过其持久化的rdb文件来实现的,其过程是先dump出rdb文件,将rdb文件全量传输给slave,然后再将dump后的操作实时同步到slave中。让从服务器(slave server)成为主服务器(master server)的精确复制品。
以下是关于 Redis 复制功能的几个重要方面:
(1)Redis 使用异步复制。从Redis 2.8 开始,从服务器会以每秒一次的频率向主服务器报告复制流(replication stream)的处理进度。
(2)一个主服务器可以有多个从服务器。
(3)不仅主服务器可以有从服务器,从服务器也可以有自己的从服务器,多个从服务器之间可以构成一个图状结构。
(4)复制功能不会阻塞主服务器:即使有一个或多个从服务器正在进行初次同步,主服务器也可以继续处理命令请求。
(5)复制功能也不会阻塞从服务器:只要在
redis.conf
文件中进行了相应的设置,即使从服务器正在进行初次同步, 服务器也可以使用旧版本的数据集来处理命令查询。不过, 在从服务器删除旧版本数据集并载入新版本数据集的那段时间内,连接请求会被阻塞。你还可以配置从服务器,让它在与主服务器之间的连接断开时,向客户端发送一个错误。(6)可以通过复制功能来让主服务器免于执行持久化操作:只要关闭主服务器的持久化功能,然后由从服务器去执行持久化操作即可。
二、原理
redis的主从复制分为两个阶段:
(1)同步操作: 将从服务器的数据库状态更新至主服务器当前所处的数据库状态。
(2)命令传播: 在主服务器的数据库状态被修改,导致主从服务器的数据库状态出现不一致时,让主从服务器重新回到一致状态。
无论是初次连接还是重新连接, 当建立一个从服务器时, 从服务器都将向主服务器发送一个 SYNC 命令。
1.同步
从服务器对主服务器的同步操作需要通过向主服务器发送 SYNC 命令来完成, 以下是 SYNC 命令的执行步骤:
(1)从服务器向主服务器发送 SYNC 命令。
(2)收到 SYNC 命令的主服务器执行 BGSAVE 命令, 在后台生成一个 RDB 文件, 并使用一个缓冲区记录从现在开始执行的所有写命令。
(3)当主服务器的 BGSAVE 命令执行完毕时, 主服务器会将 BGSAVE 命令生成的 RDB 文件发送给从服务器, 从服务器接收并载入这个 RDB 文件, 将自己的数据库状态更新至主服务器执行 BGSAVE 命令时的数据库状态。
(4)主服务器将记录在缓冲区里面的所有写命令发送给从服务器, 从服务器执行这些写命令, 将自己的数据库状态更新至主服务器数据库当前所处的状态。
下图展示了 SYNC 命令执行期间, 主从服务器的通信过程:
2.命令传播
在同步操作执行完毕之后, 主从服务器两者的数据库达到一致状态, 但这种一致并不是一成不变的。当主服务器执行客户端发送的写命令时,主服务器的数据库就有可能会被修改, 并导致主从服务器状态不再一致。为了让主从服务器再次回到一致状态,主服务器需要对从服务器执行命令传播操作: 主服务器会将自己执行的写命令 —— 即造成主从服务器不一致的那条写命令发送给从服务器执行, 当从服务器执行了相同的写命令之后, 主从服务器将再次回到一致状态。但是这样的复制功能有缺陷:在主从服务器断线重连之后执行同步动作时,生成完整的RDB文件并且发送到从服务器载入,但主从服务器的数据库状态在断线前基本上是一致的,不一致的部分只有断线后主服务器执行那一部分修改数据库的命令,所以这时SYNC命令就非常浪费,因为生成RDB文件时一个非常消耗CPU、内存和IO资源的过程,发送RDB文件到从服务器会占用大量的网络带宽资源,从服务器在载入RDB文件的过程中会阻塞不会响应任何命令,所以大部分情况下执行SYNC命令是没有必要也是非常不合理的。
为了解决2.8之前版本SYNC命令的性能问题,2.8版本设计了一个新的命令PSYNC,PSYNC命令分为完整重同步和部分重同步,完整重同步过程用于从服务器初始化时初次复制的情况和SYNC命令基本一致,PSYNC则用于断线后重新复制,在条件允许的情况下,它不会生成RDB文件,而是给从服务器回复一个+Continue表示执行部分重同步,并且把从服务器断线后主服务器执行的修改数据库的命令发送到从服务器,从服务器执行这些命令同步数据库。
部分重同步功能由下面几个部分构成:
- 主服务器的复制偏移量和从服务器的复制偏移量:当主服务器在向从服务器进行命令同步时,主服务器和从服务器会各自记录一个复制偏移量,当主从服务器的数据库状态一致时这两个复制偏移量是相同的,如果这两个偏移量不一致说明当前主从服务器的状态不一致。
- 主服务器的复制积压缓冲区:复制积压缓冲区是一个固定大小的FIFO队列,当队列已满时会弹出最早插入的数据,在主服务器进行命令传播时会同时把命令放到缓冲区中,缓冲区包含两部分数据,偏移量和字节。在进行复制时从服务器会将偏移量上报到主服务器,主服务检查当前偏移量是否还存在缓冲区中,如果存在进行部分重同步,如果不存在进行完整重同步。因为这个积压缓冲区是一个固定大小的队列,所以当从服务器长时间断线时,从服务器的复制偏移量很可能已不再缓冲区中,这时候只能进行完整重同步。
- 服务器的运行ID:初次同步时主服务器会把ID发给从服务器,从服务器保存主服务器ID,当断线重连后,会把之前保存的主服务器ID上报给主服务器,主服务器检查从服务器之前复制的主服务器ID是否和自己的ID相同,如果相同,执行部分重同步,如果不同说明从服务器之前记录的状态不是当前主服务器,这时候需要执行完整重同步。
PSYNC命令实现
初始复制或者之前执行过SLAVEOF no one命令,执行完整重同步:发送PSYNC ? -1命令到主服务器。
如果从服务器已经复制过某个主服务器,在开始新复制时向主服务器发送PSYNC <runid> <offset>命令,runid是上次复制的主服务器id,offset是从服务器的复制偏移量,主服务器会根据这个两个参数来决定做哪种同步,判断服务器id是否和本机相同,复制偏移量是否在缓冲区中,主服务器有三种回复:
- 回复+FULLRESYNC <runid> <offset>执行完整重同步,从服务器把offset当做初始复制偏移量
- 回复+CONTINUE,表示执行部分重同步,从服务器等待主服务器发送缺少的数据
- 回复-ERR,表示主服务器版本低于2.8,不支持PSYNC命令
新版本复制过程:
- 设置主服务器地址和端口,通过调用SAVEOF <master_ip> <master_port>命令。
- 建立套接字连接。
- 发送PING命令,检查主从服务器是否能够正常处理命令。
- 身份验证,从服务器设置了masterauth并且主服务器设置了requirepass是需要进行身份验证。这两个选项要么都设置要么都不设置,如果只设置了一个从服务器向主服务器发送命令时会报错。
- 发送端口信息,通过执行命令REPLCONF listening-port <port-number>,向主服务器发送从服务器的监听端口号。
- 同步,从服务器向主服务器发送PSYNC命令。
- 命令传播,完成同步之后主服务器会把之后执行的写命令传播到从服务器保证主从服务器的状态一致。
心跳检测
在命令传播阶段,从服务器默认每秒一次的频率向主服务器发送命令:REPLCONF ACK <replication_offset>,replication_offset是从服务器的复制偏移量,该命令有三个作用:
- 检测从服务器的网络连接状态,检测主从服务器连接是否正常,如果主服务器超过一定时间没有收到从服务器的REPLCONF ACK 命令,那么它们的连接可能出了问题。
- 辅助实现min-slaves选项,min-slaves-to-write和min-slaves-max-lag两个选项可以防止主服务器在不安全的情况下执行写命令,min-slaves-to-write 3 min-slaves-max-lag 10 表示如果从服务器少于3个,或者3个从服务器的延迟都大于10秒时,主服务器拒绝写命令。
- 检测命令丢失,主服务器接收到从服务器的REPLCONF ACK 命令之后会检查从服务器的偏移量是否和主服务器的一致,如果不一致会把积压缓冲区中的从服务器偏移量后面的命令发送到从服务器。
三、其他
1.关闭主服务器持久化时,复制功能的数据安全
当配置Redis复制功能时,强烈建议打开主服务器的持久化功能。 否则的话,由于延迟等问题,部署的服务应该要避免自动拉起。
为了帮助理解主服务器关闭持久化时自动拉起的危险性,参考一下以下会导致主从服务器数据全部丢失的例子:
1. 假设节点A为主服务器,并且关闭了持久化。 并且节点B和节点C从节点A复制数据
2. 节点A崩溃,然后由自动拉起服务重启了节点A. 由于节点A的持久化被关闭了,所以重启之后没有任何数据
3. 节点B和节点C将从节点A复制数据,但是A的数据是空的, 于是就把自身保存的数据副本删除。
在关闭主服务器上的持久化,并同时开启自动拉起进程的情况下,即便使用Sentinel来实现Redis的高可用性,也是非常危险的。 因为主服务器可能拉起得非常快,以至于Sentinel在配置的心跳时间间隔内没有检测到主服务器已被重启,然后还是会执行上面的数据丢失的流程。
无论何时,数据安全都是极其重要的,所以应该禁止主服务器关闭持久化的同时自动拉起。
2.只读从服务器
从Redis 2.6开始,从服务器支持只读模式,并且该模式为从服务器的默认模式。
只读模式由
redis.conf
文件中的slave-read-only
选项控制, 也可以通过CONFIG SET命令来开启或关闭这个模式。只读从服务器会拒绝执行任何写命令,所以不会出现因为操作失误而将数据不小心写入到了从服务器的情况。
即使从服务器是只读的,
DEBUG
和CONFIG
等管理式命令仍然是可以使用的,所以我们还是不应该将服务器暴露给互联网或者任何不可信网络。不过,使用redis.conf
中的命令改名选项,我们可以通过禁止执行某些命令来提升只读从服务器的安全性。你可能会感到好奇,既然从服务器上的写数据会被重同步数据覆盖,也可能在从服务器重启时丢失,那么为什么要让一个从服务器变得可写呢。
原因是,一些不重要的临时数据,仍然是可以保存在从服务器上面的。 比如说,客户端可以在从服务器上保存主服务器的可达性(reachability)信息,从而实现故障转移(failover)策略。
3.从服务相关配置
如果主服务器通过 requirepass
选项设置了密码, 那么为了让从服务器的同步操作可以顺利进行, 我们也必须为从服务器进行相应的身份验证设置。
对于一个正在运行的服务器, 可以使用客户端输入以下命令:
要永久地设置这个密码, 那么可以将它加入到配置文件中:
4.主服务器只在有至少 N 个从服务器的情况下,才执行写操作
从Redis 2.8开始,为了保证数据的安全性,可以通过配置,让主服务器只在有至少N个当前已连接从服务器的情况下,才执行写命令。
不过,因为Redis使用异步复制,所以主服务器发送的写数据并不一定会被从服务器接收到,因此,数据丢失的可能性仍然是存在的。
以下是这个特性的运作原理:
- 从服务器以每秒一次的频率 PING 主服务器一次,并报告复制流的处理情况。
- 主服务器会记录各个从服务器最后一次向它发送PING的时间。
- 用户可以通过配置,指定网络延迟的最大值
min-slaves-max-lag
,以及执行写操作所需的至少从服务器数量min-slaves-to-write
。如果至少有
min-slaves-to-write
个从服务器, 并且这些服务器的延迟值都少于min-slaves-max-lag
秒, 那么主服务器就会执行客户端请求的写操作。你可以将这个特性看作CAP理论中的C的条件放宽版本:尽管不能保证写操作的持久性,但起码丢失数据的窗口会被严格限制在指定的秒数中。
另一方面,如果条件达不到
min-slaves-to-write
和min-slaves-max-lag
所指定的条件, 那么写操作就不会被执行, 主服务器会向请求执行写操作的客户端返回一个错误。以下是这个特性的两个选项和它们所需的参数:
min-slaves-to-write <number of slaves>
min-slaves-max-lag <number of seconds>
5.配置
参考: