《Reids 设计与实现》第十二章 复制

文章目录

  • 《Reids 设计与实现》第十二章 复制
  • 一、简介
  • 二、旧版复制功能的实现
    • 1.同步
    • 2.命令传播
  • 三、旧版复制功能的缺陷
  • 四、新版复制功能的实现
  • 五、部分重同步的实现
    • 1.复制偏移量
    • 2.复制积压缓冲区
    • 3.服务器运行 ID
  • 六、PSYNC 命令的实现
  • 七、复制的实现
    • 1.步骤 1:设置主服务器的地址和端口
    • 2.步骤 2:建立套接字连接
    • 3.步骤 3:发送 PING 命令
    • 4.步骤 4:身份验证
    • 5.步骤 5:发送端口信息
    • 6.步骤 6:同步
    • 7.步骤 7:命令传播
  • 八、心跳检测
    • 1.检测主从服务器的网络连接状态
    • 2.辅助实现 min-slaves 配置选项
    • 3.检测命令丢失
  • 九、重点回顾

一、简介

在 Redis 中,用户可以通过执行 SLAVEOF 命令或者设置 slaveof 选项,让一个服务器去复制(replicate)另一个服务器,我们称呼被复制的服务器为主服务器(master),而对主服务器进行复制的服务器则被称为从服务器(slave)

假设现在有两个 Redis 服务器,地址分别为 127.0.0.1:6379 和 127.0.0.1:12345,如果我们向服务器 127.0.0.1:12345 发送以下命令:

127.0.0.1:12345>SLAVEOF 127.0.0.1 6379
OK

那么服务器 127.0.0.1:12345 将成为 127.0.0.1:6379 的从服务器,而服务器 127.0.0.1:6379 则会成为 127.0.0.1:12345 的主服务器

进行复制中的主从服务器双方的数据库将保存相同的数据,概念上将这种现象称作 “数据库状态一致”,或者简称 “一致”

比如说,如果我们在主服务器上执行以下命令:

127.0.0.1:6379>SET msg "hello world"
OK

那么我们应该既可以在主服务器上获取 msg 键的值:

127.0.0.1:6379>GET msg
"hello world"

又可以在从服务上获取 msg 键的值:

127.0.0.1:12345>GET msg
"hello world"

另一方面,如果我们在主服务器中删除了键 msg:

127.0.0.1:6379>DEL msg
(integer) 1

那不仅主服务器上的 msg 键会被删除:

127.0.0.1:6379>EXISTS msg
(integer) 0

从服务器上的 msg 键也应该会被删除

127.0.0.1:12345>EXISTS msg
(integer) 0

关于复制的特性和用法还有很多,Redis 官方网站上的《复制》文档(http://redis.io/topics/replication)已经做了很详细的介绍,这里不再赘述

二、旧版复制功能的实现

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

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

1.同步

当客户端向从服务器发送 SLAVEOF 命令,要求从服务器复制主服务器时,从服务器首先需要执行同步操作,也即是,将从服务器的数据库状态更新至主服务器当前所处的数据库状态

从服务对主服务器的同步操作需要通过向主服务器发送 SYNC 命令来完成,以下是 SYNC 命令的执行步骤:

  1. 从服务器向主服务器发送 SYNC 命令
  2. 收到 SYNC 命令的主服务器执行 BGSAVE 命令,在后台生成一个 RDB 文件,并使用一个缓冲区记录从现在开始执行的所有写命令
  3. 当主服务器的 BGSAVE 命令执行完毕时,主服务器会将 BGSAVE 命令生成的 RDB 文件发送给从服务器,从服务器接收并载入这个 RDB 文件,将自己的数据库状态更新至主服务器执行 BGSAVE 命令时的数据库状态
  4. 主服务器将记录在缓冲区里面的所有写命令发送给从服务器,从服务器执行这些写命令,将自己的数据库状态更新至主服务器数据库当前所处的状态

图 15-2 展示了 SYNC 命令执行期间,主从服务器的通信过程

表 15-1 展示了一个主从服务器进行同步的例子

2.命令传播

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

为了让主从服务器再次回到一致状态,主服务器需要对从服务器执行命令传播操作:主服务器会将自己执行的写命令,也即是造成主从服务器不一致的那条写命令,也即是造成主从服务器不一致的那条写命令,发送给从服务器执行,当从服务器执行了相同的写命令之后,主从服务器将再次回到一致状态

三、旧版复制功能的缺陷

在 Redis 2.8 以前, 从服务器对主服务器的复制可以分为以下两种情况:

  • 初次复制:从服务器以前没有复制过任何主服务器,或者从服务器当前要复制的主服务器和上一次复制的主服务器不同
  • 断线后重复制:处于命令传播阶段的主从服务器因为网络原因而中断了复制,但从服务器通过自动重连接连上了主服务器,并继续复制主服务器

对于初次复制来说,旧版复制功能能够很好地完成任务,但对于断线后重复制来说,旧版复制功能虽然也能让主从服务器重新回到一致状态,但效率却非常低

要理解这一情况,请看表 15-2 展示的断线后重复制例子

在时间 T10091,从服务器终于重新连接上主服务器,因为这时主从服务器的状态已经不再一致,所以从服务器将向主服务器发送 SYNC 命令,而主服务器会将包含键 k1 至键 k10089 的 RDB 文件发送给从服务器,从服务器通过接收和载入这个 RDB 文件来将自己的数据库更新至主服务器数据库当前所处的状态

虽然再次发送 SYNC 命令可以让主从服务器重新回到一致状态,但如果我们仔细研究这个断线重复制过程,就会发现传送 RDB 文件这一步并不是非做不可的:

  • 主从服务器在时间 T0 至时间 T10086 中一直处于一致状态,这两个服务器保存的数据大部分都是相同的
  • 从服务器想要将自己更新至主服务器当前所处的状态,真正需要的时主从服务器连接中断期间,主服务器新添加的 k10087、k10088、k10089 三个键的数据
  • 可惜的是,旧版复制功能并没有利用以上列举的两点条件,而是继续让主服务器生成并向从服务器发送包含键 k1 至 k10089 的 RDB 文件,但实际上 RDB 文件包含的键 k1 至键 k10086 的数据对于从服务器来说都是不必要的

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

每次执行 SYNC 命令,主从服务器需要执行以下动作:

  1. 主服务器需要执行 BGSAVE 命令来生成 RDB 文件,这个生成操作会耗费主服务器大量的 CPU、内存和磁盘 I/O 资源
  2. 主服务器需要将自己生成的 RDB 文件发送给从服务器,这个发送操作会耗费主从服务器大量的网络资源(带宽和流量),并对主服务器响应命令请求的时间产生影响
  3. 接收到 RDB 文件的从服务器需要载入主服务器发来的 RDB 文件,并且在载入期间,从服务器会因为阻塞而没办法处理命令请求

因为 SYNC 命令是一个如此耗费资源的操作,所以 Redis 有必要保证在真正有需要时才执行 SYNC 命令

四、新版复制功能的实现

为了解决旧版复制功能在处理断线复制情况时的低效问题,Redis 从 2.8 版本开始,使用 PSYNC 命令代替 SYNC 命令来执行复制时的同步操作

PSYNC 命令具有完整重同步(full resynchronization)和部分重同步(partial resynchronization)两种模式:

  • 其中完整重同步用于处理初次复制情况:完整重同步的执行步骤和 SYNC 命令的执行步骤基本一样,它们都是通过让主服务器创建并发送 RDB 文件,以及向从服务器发送保存在缓冲区里面的写命令来进行同步
  • 而部分重同步则用于处理断线后重复制情况:当从服务器在断线后重新连接主服务器时,如果条件允许,主服务器可以将主从服务器连接断开期间执行的写命令发送给从服务器,从服务器只要接收并执行这些写命令,就可以将数据库更新至主服务器当前所处的状态

PSYNC 命令的部分重同步模式解决了旧版复制功能在处理断线后重复制时出现的低效情况,表 15-3 展示了如何使用 PSYNC 命令高效地处理上一节展示的断线后复制情况

对比一下 SYNC 命令和 PSYNV 名来处理断线后复制的方法,不难看出,虽然 SYNC 命令和 PSYNC 命令都可以让断线的主从服务器重新回到一致状态,但执行部分重同步所需的资源比起执行 SYNC 命令所需的资源要少得多,完成同步的速度也快得多。执行 SYNC 命令需要生成、传送和载入整个 RDB 文件,而部分重同步只需要将从服务器缺少的写命令发送给从服务器执行就可以了

图 15-6 展示了主从服务器在执行部分重同步时的通信过程

五、部分重同步的实现

部分重同步功能由以下三个部分构成:

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

1.复制偏移量

执行复制的双方 —— 主服务器和从服务器会分别维护一个复制偏移量:

  • 主服务器每次向从服务器传播 N 个字节的数据时,就将自己的复制偏移量的值加上 N
  • 从服务器每次收到主服务器传播来的 N 个字节的数据时,就将自己的复制偏移量的值加上 N

在图 15-7 所示的例子中,主从服务器的复制偏移量的值都为 10086

如果这时主服务器向三个从服务器传播长度为 33 字节的数据,那么主服务器的复制偏移量将更新为 10086+33=10119,而三个从服务器在接收到主服务器传播的数据之后,也会将复制偏移量更新为 10119,如图 15-8 所示

通过对比主从服务器的复制偏移量,程序可以很容易地直到主从服务器是否处于一致状态:

  • 如果主从服务器处于一致状态,那么主从服务器两者的偏移量总是相同的
  • 相反,如果主从服务器两者的偏移量并不相同,那么说明主从服务器并未处于一致状态

考虑以下这个例子:假设如图 15-7 所示,主从服务器当前的复制偏移量都为 10086,但是就在主服务器要向从服务器传播长度为 33 字节的数据之前,从服务器 A 断线了,那么主服务器传播的数据将只有从服务器 B 和从服务器 C 能收到,在这之后,主服务器、从服务器 B 和从服务器 C 三个服务器的复制偏移量都将更新为 10119,而断线后的从服务器 A 的复制偏移量仍然停留在 10086,这说明从服务器 A 与主服务器并不一致,如图 15-9 所示

假设从服务器 A 在断线之后就立即重新连接主从服务器,并且成功,那么接下来,从服务器将向主服务器发送 PSYNC 命令,报告从服务器 A 当前的复制偏移量为 10086,那么这时,主服务器应该对从服务器执行完整重同步还是部分重同步呢?如果执行部分重同步的话,主服务器又如何补偿从服务器 A 在断线期间丢失的那部分数据呢?以上问题的答案都和复制积压缓冲区有关

2.复制积压缓冲区

复制积压缓冲区是由主服务器维护的一个固定长度(fixed-size)先进先出(FIFO)队列,默认大小为 1MB

固定长度先进先出队列

固定长度先进先出队列的入队和出队规则跟普通的先进先出队列一样:新元素从一边进入队列,而旧元素从另一边弹出队列

和普通先进先出队列随着元素的增加和减少而动态调整长度不同,固定长度先进先出队列的长度是固定的,当入队元素的数量大于队列长度时,最先入队的元素会被弹出,而新元素会被放入队列

举个例子,如果我们要将 ‘h’、‘e’、‘l’、‘l’、‘o’ 五个字符放进一个长度为 3 的固定长度先进先出队列里面,那么 ‘h’、‘e’、‘l’ 三个字符将首先被放入队列:

 ['h'、'e'、'l']

但是当后一个 ‘l’ 字符要进入队列时,队首的 ‘h’ 字符将被弹出,队列变成:

 ['e'、'l'、'l']

接着,‘o’ 的入队会引起 ‘e’ 的出队,队列变成:

 ['l'、'l'、'o']

以上就是固定长度先进先出队列的运作方式

当主服务器进行命令传播时,它不仅会将写命令发送给所有从服务器,还会将写命令入队到复制积压缓冲区里面,如图 15-10 所示

因此,主服务的复制积压缓冲区里面会保存着一部分最近传播的写命令,并且复制积压缓冲区会为队列中的每个字节记录相应的复制偏移量,就像表 15-4 展示的那样

当从服务器重新连上主服务器,从服务器会通过 PSYNC 命令将自己的复制偏移量 offset 发送给主服务器,主服务器会根据这个复制偏移量来决定对从服务器执行何种同步操作:

  • 如果 offset 偏移量之后的数据(也即是 ofset+1 开始的数据)仍然存在于复制积压缓冲区里面,那么主服务器将对从服务器执行部分重同步操作
  • 相反,如果 offset 偏移量之后的数据已经不存在于复制积压缓冲区,那么主服务器将对从服务器执行完整重同步操作

回到之前图 15-9 展示的断线后重连接例子:

  • 当从服务器 A 短线之后,它立即重连接主服务器,并向主服务器发送 PSYNC 命令,报告自己的复制偏移量为 10086
  • 主服务器收到从服务器发来的 PSYNC 命令以及偏移量 10086 之后,主服务器将检查偏移量 10086 之后的数据是否存在于复制积压缓冲区里面,结果发现这些数据仍然存在,于是主服务器向从服务器发送 +COUNTINUE 回复,表示数据同步将以部分重同步模式来进行
  • 接着主服务器会将复制积压缓冲区 10086 偏移量之后的所有数据(偏移量为 10087 至 10119)都发送给从服务器
  • 从服务器只要接收这 33 字节的缺失数据,就可以回到与主服务器一致的状态,如图 15-11 所示

根据需要调整复制积压缓冲区的大小

Redis 为复制积压缓冲区设置的默认大小为 1MB,如果主服务器需要执行大量写命令,又或者主从服务器断线后重连接所需的时间比较长,那么这个大小也许并不合适。如果复制积压缓冲区的大小设置得不恰当,那么 PSYNC 命令的复制重同步模式就不能正常发挥作用,因此,正确估算和设置复制积压缓冲区的大小非常重要

复制积压缓冲区的最小大小可以根据公式 second * write_size_per_second 来估算:

  • 其中 second 为主从服务器断线后重新连接上主服务器所需的平均时间(以秒计算)
  • 而 write_size_per_second 则是主服务器平均每秒产生的写命令数据量(协议格式的写命令的长度总和)

例如,如果主服务器平均每秒产生 1MB 的写数据,而从服务器断线之后平均要 3 秒才能重新连接上主服务器,那么复制积压缓冲区的大小就不能低于 5MB

为了安全起见,可以将复制积压缓冲区的大小设为 2 * second * write_size_per_second,这样可以保证绝大部分断线情况都能用部分重同步来处理

至于复制积压缓冲区大小的修改方法,可以参考配置文件中关于 repl-backlog-size 选项的说明

3.服务器运行 ID

除了复制偏移量和复制积压缓冲区之外,实现部分重同步还需要用到服务器运行 ID(run ID):

  • 每个 Redis 服务器,不论主服务器还是从服务器,都会有自己的运行 ID
  • 运行 ID 在服务器启动时自动生成,由 40 个随机的十六进制字符组成,例如 53b9b28df8042fdc9ab5e3fcbbbabffld5dce2b3

当从服务器对主服务器进行初次复制时,主服务器会将自己的运行 ID 传送给从服务器,而从服务器则会将这个运行 ID 保存起来

当从服务器断线并重新连上一个主服务器时,从服务器将向当前连接的主服务器发送之前保存的运行 ID:

  • 如果从服务器保存的运行 ID 和当前连接的主服务器的运行 ID 相同,那么说明从服务器断线之前复制的就是当前连接的这个主服务器,主服务器可以继续尝试执行部分重同步操作
  • 相反地,如果从服务器保存的运行 ID 和当前连接的主服务器的运行 ID 并不相同,那么说明从服务器断线之前复制的主服务器并不是当前连接的这个主服务器,主服务器将对从服务器执行完整重同步操作

举个例子,假设从服务器原本正在复制一个运行 ID 为 53b9b28df8042fdc9ab5e3fcbbbabffld5dce2b3 的主服务器,那么网络断开,从服务器重新连接上主服务器之后,从服务器将向主服务器发送这个运行 ID,主服务器根据自己的运行 ID 是否为 53b9b28df8042fdc9ab5e3fcbbbabffld5dce2b3 来判断是执行部分重同步还是执行完整重同步

六、PSYNC 命令的实现

到目前为止,本章在介绍 PSYNC 命令时一直没有说明 PSYNV 命令的参数以及返回值,因为那时我们还未了解服务器运行 ID、复制偏移量、复制积压缓冲区这些东西,在学习了部分重同步的实现原理之后,我们现在可以来了解 PSYNC 命令的完整细节了

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

  • 如果从服务器以前没有复制过任何主服务器,或者之前执行过 SLAVEOF no one 命令,那么从服务器在开始一次新的复制时将向主服务器发送 PSYNC ? -1 命令,主动请求主服务器进行完整重同步(因为这时不可能执行部分重同步)
  • 相反地,如果从服务器已经复制过某个主服务器,那么从服务器在开始一次新的复制时将向主服务器发送 PSYNC <runid> <offset> 命令:其中 runid 是上一次复制的主服务器的运行 ID,而 offset 则是从服务器当前的复制偏移量,接收到这个命令的主服务器会通过这两个参数来判断应该对从服务器执行哪种同步操作

根据情况,接收到 PSYNC 命令的主服务器会向从服务器返回以下三种回复的其中一种:

  • 如果主服务器返回 +FULLRESYNC <runid> <offset> 回复,那么表示主服务器将与从服务器执行完整重同步操作:其中 runid 是这个主服务器的运行 ID,从服务器会将这个 ID 保存起来,在下一次发送 PSYNC 命令时使用;而 offset 则是主服务器当前的复制偏移量,从服务器会将这个值作为自己的初始化偏移量
  • 如果主服务器返回 +CONTINUE 回复,那么表示主服务器将与从服务器执行部分重同步操作,从服务器只要等着主服务器将自己缺少的那部分数据发送过来就可以了
  • 如果主服务器返回 -ERR 回复,那么表示主服务器的版本低于 Redis 2.8,它识别不了 PYSNC 命令,从服务器将向主服务器发送 SYNC 命令,并与主服务器执行完整同步操作

流程图 15-12 总结了 PSYNC 命令执行完整重同步和部分重同步时可能遇上的情况

七、复制的实现

通过向从服务器发送 SLAVEOF 命令,我们可以让一个从服务器去复制一个主服务器:

SLAVEOF <master_ip> <master_port>

本节将以从服务器 127.0.0.1:12345 接收命令:

SLAVEOF 127.0.0.1 6379

为例,展示 Redis 2.8 或以上版本的复制功能的详细实现步骤

1.步骤 1:设置主服务器的地址和端口

当客户端向从服务器发送以下命令时:

127.0.0.1:12345>SLAVEOF 127.0.0.1 6379
OK

从服务器首先要做的就是将客户端给定的主服务器 IP 地址 127.0.0.1 以及端口 6379 保存到服务器状态的 masterhost 属性和 masterport 属性里面:

struct redisServer{//...//主服务器的地址char *masterhost://主服务器的端口int masterport;//...
{;

图 15-13 展示了 SLAVEOF 命令执行之后,从服务器的服务器状态

SLAVEOF 命令是一个异步命令,在完成 masterhost 属性和 masterport 属性的设置工作之后,从服务器将向发送 SLAVEOF 命令的客户端返回 OK,表示复制指令已经被接收,而实际的复制工作将在 OK 返回之后才真正开始执行

2.步骤 2:建立套接字连接

在 SLAVEOF 命令执行之后,从服务器将根据命令所设置的 IP 地址和端口,创建连向主服务器的套接字连接

如果从服务器创建的套接字能成功连接(connect)到主服务器,那么从服务器将为这个套接字关联一个专门用于处理复制工作的文件事件处理器,这个处理器将负责执行后续的复制工作,比如接收 RDB 文件,以及接收主服务器传播来的写命令,诸如此类

而主服务器在接受(accept)从服务器的套接字连接之后,将为该套接字创建相应的客户端状态,并将从服务器看作是一个连接到主服务器的客户端来对待,这时从服务器将同时具有服务器(server)和客户端(client)两个身份:从服务器可以向主服务器发送命令请求,而主服务器则会向从服务器返回命令回复

因为复制工作接下来的几个步骤都会以从服务器向主服务器发送命令请求的形式来进行,所以理解 “从服务器是主服务器的客户端” 这一点非常重要

3.步骤 3:发送 PING 命令

从服务器成为主服务器的客户端之后,做的第一件事就是向主服务器发送一个 PING 命令

这个 PING 命令有两个作用:

  • 虽然主从服务器成功建立起了套接字连接,但双方并未使用该套接字进行过任何通信,通过发送 PING 命令可以检查套接字的读写状态是否正常
  • 因为复制工作接下来的几个步骤都必须在主服务器可以正常处理命令请求的状态下才能进行,通过发送 PING 命令可以检查主服务器能否正常处理命令请求

从服务器在发送 PING 命令之后将遇到以下三种情况中的其中一种:

  • 如果主服务器向从服务器返回了一个命令回复,但从服务器却不能在规定的时限内(timeout)内读取出命令回复的内容,那么表示主从服务器之间的网络连接状态不佳,不能继续执行复制工作的后续步骤。当出现这种情况时,从服务器断开并重新创建连向主服务器的套接字
  • 如果主服务器向从服务器返回一个错误,那么表示主服务器暂时没办法处理从服务器的命令请求,不能继续执行复制工作的后续步骤。当出现这种情况时,从服务器断开并重新创建连向主服务器的套接字。比如说,如果主服务器正在处理一个超时运行的脚本,那么当从服务器向主服务器发送 PING 命令时,从服务器将收到主服务器返回的 BUSY Redisis busy running a script,You can only call SCRIPT KILL or SHUTDOWN NOSAVE. 错误
  • 如果从服务器读取到 “PONG” 回复,那么表示主从服务器之间的网络连接状态正常,并且主服务器(客户端)发送的命令请求,在这种情况下,从服务器可以继续执行复制工作的下个步骤

流程图 15-17 总结了从服务器在发送 PONG 命令时可能遇到的情况,以及各个情况的处理方式

4.步骤 4:身份验证

从服务器在收到主服务器返回的 “PONG” 回复之后,下一步要做的就是决定是否进行身份验证:

  • 如果从服务器设置了 masterauth 选项,那么进行身份验证
  • 如果从服务器没有设置 masterauth 选项,那么不进行身份验证

在需要进行身份验证的情况下,从服务器将向主服务器发送一条 AUTH 命令,命令的参数为从服务器 masterauth 选项的值

举个例子,如果从服务器 masterauth 选项的值为 10086,从服务器在身份验证阶段可能遇到的情况有以下几种:

  • 如果主服务没有设置 requirepass 选项,并且从服务器也没有设置 masterauth 选项,那么主服务器将继续执行从服务器发送的命令,复制工作可以继续进行
  • 如果从服务器通过 AUTH 命令发送的密码和主服务器 requirepass 选项所设置的密码相同,那么主服务器将继续执行从服务器发送的命令,复制工作可以继续进行。与此相反,如果主从服务器设置得密码不相同,那么主服务器将返回一个 invalid password 错误
  • 如果主服务器设置了 requirepass 选项,但从服务器却没有设置 masterauth 选项,那么主服务器将返回一个 NOAUTH 错误。另一方面,如果主服务器没有设置 require pass选项,但从服务器却设置了 masterauth 选项,那么主服务器将返回一个 no password is set 错误

所有错误情况都会令从服务器中止目前的复制工作,并从创建套接字开始重新执行复制,直到身份验证通过,或者从服务器放弃执行复制为止

流程图 15-19 总结了从服务器在身份验证阶段可能遇到的情况,以及各个情况的处理方式

5.步骤 5:发送端口信息

在身份验证步骤之后,从服务器将执行命令 REPLCONF listening-port <port-number>,向主服务器发送从服务器的监听端口号

例如在我们的例子中,从服务器的监听端口为 12345,那么从服务器将向主服务器发送命令 REPLCONF listening-port 12345。主服务器在接收到这个命令之后,会将端口号记录在从服务器所对应的客户端状态的 slave_listening_port 属性中:

typedef struct redisClient{//...//从服务器的监听端口号int slave_listening_port;//...
}redisClient;

图 15-21 展示了客户端状态设置 slave_listening_port 属性之后的样子

6.步骤 6:同步

在这一步,从服务器将向主服务器发送 PSYNC 命令,执行同步操作,并将自己的数据库更新至主服务器数据库当前所处的状态

值得一提的是,在同步操作执行之前,只有从服务器是主服务器的客户端,但是在执行同步操作之后,主服务器也会成为从服务器的客户端

因此,在同步操作执行之后,主从服务器都是对方的客户端,它们可以互相向对方发送命令请求,或者互相向对方返回命令回复,如图 15-22 所示

正因为主服务器成为了从服务器的客户端,所以主服务器才可以通过发送写命令来改变从服务器的数据库状态,不仅同步操作需要用到这一点,这也是主服务器对从服务器执行命令传播操作的基础

7.步骤 7:命令传播

当完成了同步之后,主从服务器就会进入命令传播阶段,这时主服务器只要一直将自己执行的写命令发送给从服务器,而从服务器只要一直接受并执行主服务器发来的写命令,就可以保证主从服务器一直保持一致了

八、心跳检测

在命令传播阶段,从服务器默认会以每秒一次的频率,向主服务器发送命令:

REPLCONF ACK<replication_offset>

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

发送 REPLCONF ACK 命令对于主从服务器有三个作用:

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

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

主从服务器可以通过发送和接受 REPLCONF ACK 命令来检查两者之间的网络连接是否正常:如果主服务器超过一秒钟没有收到从服务器发来的 REPLCONF ACK 命令,那么主服务器就知道主从服务器之间的连接出现问题了

通过向主服务器发送 INFO replication 命令,在列出的从服务器列表的 lag 一栏中,我们可以看到相应从服务器最后一次向主服务器发送 REPLCONF ACK 命令距离现在过了多少秒

在一般情况下,lag 的值应该在 0 秒或者 1 秒之间跳动,如果超过 1 秒的话,那么说明主从服务器之间的连接出现了故障

2.辅助实现 min-slaves 配置选项

Redis 的 min-slaves-to-write 和 min-slaves-max-lag 两个选项可以防止主服务器在不安全的情况下执行写命令

举个例子,如果我们向主服务器提供以下设置:

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

那么在从服务器的数量少于 3 个,或者三个从服务器的延迟(lag)值都大于或等于 10 秒时,主服务器将拒绝执行写命令,这里的延迟值就是上面提到的 INFO replication 命令的 lag 值

3.检测命令丢失

如果因为网络故障,主服务器传播给从服务器的写命令在半路丢失,那么当从服务器向主服务器发送 REPLCONF ACK 命令时,主服务器将发掘从服务器当前的复制偏移量少于自己的复制偏移量,然后主服务器就会根据从服务器提交的复制偏移量,在复制积压缓冲区里面找到从服务器缺少的数据,并将这些数据重新发送给从服务器

举个例子,假设有两个处于一致状态的主从服务器,它们的复制偏移量都是 200。如果这时主服务器执行了命令 SET key value (协议格式的长度为 33 字节),将自己的复制偏移量更新到了 233,并尝试向从服务器传播命令 SET key value,但这条命令却因为网络故障而在传播的途中丢失,那么主从服务器之间的复制偏移量就会出现不一致,主服务器的复制偏移量会被更新为 233,而从服务器的复制偏移量仍然为 200

在这之后,当从服务器向主服务器发送 REPLCONF ACK 命令的时候,主服务器会察觉从服务器的复制偏移量依然为 200,而自己的复制偏移量为 233,这说明复制积压缓冲区里面复制偏移量为 201 至 233 的数据(也即是 SET key value)在传播过程中丢失了,于是主服务器会再次向从服务器传播命令 SET key value,从服务器通过接收并执行这个命令可以将自己更新至主服务器当前所处的状态

注意,主服务器向从服务器补发缺失数据这一操作的原理和部分重同步操作的原理非常相似,这两个操作的区别在于,补发缺失数据操作在主从服务器没有断线的情况下执行,而部分重同步操作则在主从服务器断线并重连之后执行

Redis 2.8 版本以前的命令丢失

REPLCONF ACK 命令和复制积压缓冲区都是 Redis 2.8 版本新增的,在 Redis 2.8 版本以前, 即使命令在传播过程中丢失,主服务器和从服务器都不会注意到,主服务器更不会向从服务器补发丢失的数据,所以为了保证复制时主从服务器的数据一致性,最好使用 2.8 或以上版本的 Redis

九、重点回顾

  • Redis 2.8 以前的复制功能不能高效地处理断线后重复制情况,但 Redis 2.8 新添加的部分重同步功能可以解决这个问题
  • 部分重同步通过复制偏移量、复制积压缓冲区、服务器运行 ID 三个部分来实现
  • 在复制操作刚开始的时候,从服务器会成为主服务器的客户端,并通过向主服务器发送命令请求来执行复制步骤,而在复制操作的后期,主从服务器会互相成为对方的客户端
  • 主服务器通过向从服务器传播命令来更新从服务器的状态,保持主从服务器一致,而从服务器则通过向主服务器发送命令来进行心跳检测,以及命令丢失检测

《Reids 设计与实现》第十二章 复制相关推荐

  1. 游戏设计的艺术:一本透镜的书——第十二章 游戏机制支撑谜题

    这是一本游戏设计方面的好书 转自天之虹的博客:http://blog.sina.com.cn/jackiechueng 感谢天之虹的无私奉献 Word版可到本人的资源中下载 第十二章 游戏机制支撑谜题 ...

  2. 游戏设计的艺术:一本透镜的书——第二十二章 其他玩家往往会形成社区

    这是一本游戏设计方面的好书 转自天之虹的博客:http://blog.sina.com.cn/jackiechueng 感谢天之虹的无私奉献 Word版可到本人的资源中下载 第二十二章其他玩家往往会形 ...

  3. 《Reids 设计与实现》第十三章 Sentinel

    <Reids 设计与实现>第十三章 Sentinel 文章目录 <Reids 设计与实现>第十三章 Sentinel 一.简介 二.启动并初始化 Sentinel 1.初始化服 ...

  4. 《Reids 设计与实现》第五章 对象

    <Reids 设计与实现>第五章 对象 文章目录 <Reids 设计与实现>第五章 对象 一.简介 二.对象的类型与编码 1.类型 2.编码和底层实现 三.字符串对象 1.编码 ...

  5. stm32l0的停止模式怎么唤醒_探索者 STM32F407 开发板资料连载第二十二章 待机唤醒实验

    1)实验平台:alientek 阿波罗 STM32F767 开发板 2)摘自<STM32F7 开发指南(HAL 库版)>关注官方微信号公众号,获取更多资料:正点原子 第二十二章 待机唤醒实 ...

  6. 构建之法第十,十一,十二章阅读

    第十章经典用户和场景 虽说经典场景和经典用户很有必要去研究去效仿,但是随着时间发展,新的用户新的需求不断涌现,那该怎么平衡? 第十一章软件设计与实现 软件设计过程中,如何管理设计变更呢? 第十二章用户 ...

  7. stm32 文件系统dma大小_「正点原子NANO STM32F103开发板资料连载」第二十二章 DMA 实验...

    1)实验平台:[正点原子] NANO STM32F103 开发板 2)摘自<正点原子STM32 F1 开发指南(NANO 板-HAL 库版)>关注官方微信号公众号,获取更多资料:正点原子 ...

  8. 系统架构师学习笔记_第十二章_连载

    第十二章  系统安全架构设计 12.1  信息系统安全架构的简单描述 信息安全的特征 是为了保证信息的 机密性.完整性.可用性.可控性.不可抵赖性. 以风险策略为基础. 12.1.1  信息安全的现状 ...

  9. 鸟哥的Linux私房菜(服务器)- 第十二章、网络参数控管者: DHCP 服务器

    第十二章.网络参数控管者: DHCP 服务器 最近更新日期:2011/07/27 想象两种情况:(1)如果你在工作单位使用的是笔记本电脑,而且常常要带着你的笔记本电脑到处跑, 那么由第四章.连上 In ...

最新文章

  1. XCTF-高手进阶区:unserialize3
  2. matlab缺少某些变量,总提示缺少变量或函数
  3. [转载] Java:获取数组中的子数组的多种方法
  4. pdfContentByte 类 图像和文本的绝对位置
  5. linux 串口 字符 间隔,嵌入式linux编程过成中模块从串口读数需要特定的字符段并且需要每两位字符数组元素转换成一个16进制数(提取特定字符串+字符串转16进制)...
  6. [转]Android应用开发提高系列(4)——Android动态加载(上)——加载未安装APK中的类...
  7. Chrome 开发者工具(DevTools)中所有快捷方式列表
  8. Stack with max and min 查找栈中最大最小数
  9. 网络蜘蛛的工作原理及操作
  10. PXE启动错误代码一览表
  11. 强烈推荐这款能探测别人工资的黑科技!秀的我头皮发麻
  12. Word2016删除插入尾注参考文献下横线
  13. input 属性开启手机前置摄像头拍照
  14. 卡农,用敬仰和泪水思念着你~~~~~
  15. 迭代学习控制方式Simulink建模与仿真
  16. Python入门学习笔记第五章——if条件句~~~
  17. 计算机组装与维修专用周报告,《计算机组装与维护专用周》实习报告.doc
  18. dockerError processing tar file(exit status 1): no space left on device
  19. 《headfirst设计模式》读书笔记9-迭代器和组合模式
  20. 超详细的CentOS7.4下载与图文安装

热门文章

  1. Java中introduce方法_Java基础—继承
  2. bundle 安装_centos6.7安装zabbix4
  3. 九、装配bean--通过properties文件注入值
  4. minHash最小哈希原理
  5. Linux通过网卡驱动程序和版本号的信息
  6. Day6-------BOM
  7. SQL SERVER2008 存储过程、表、视图、函数的权限
  8. 新闻更新延时引发的学习,CACHE的利用。
  9. 谷歌浏览器主页_谷歌浏览器客服人工服务电话怎样查询-客服人工服务电话查询方法...
  10. linux之EXT2文件系统--理解block/block group/索引结点inode/索引位图