@[TOC](第11章 服务器的复制)


前言

参考资料:《Redis设计与实现 第二版》;

第四部分为多机数据库的实现,主要由以下模块组成:复制Sentinel集群

本篇将介绍 Redis 的复制功能。在 Redis 中,用户可以通过执行 SLAVEOF 命令或者设置 salveof 选项,让一个从服务器复制主服务器。

与本章相关的 Redis 命令总结在下篇文章,欢迎点击收藏,本篇将不再重复:

《Redis常用命令及示例总结(API)》https://blog.51cto.com/dlhjw/4744855


1. 旧版复制功能的实现

  • 旧版指 Redis 2.8 以前版本;
  • 旧版 Redis 的复制功能分为同步(sync)和命令传播(command propagate)两个操作:
    • 同步操作:将从服务器的数据库状态更新至主服务器当前所处的数据库状态;
    • 命令传播:用于在主服务器的数据库状态被修改,导致主从服务器的数据库状态出现不一致时,让主从服务器重新回到一致状态;

1.1 同步与命令传播

  • 同步:客户端向从服务器发送 SLAVEOF 命令,从服务器执行以下步骤:
    • 1)从服务器向主服务器发送 SYNC 命令;
    • 2)收到 SYNC 命令的主服务器执行 BGSAVE 命令,在后台生成 RDB 文件,并使用一个缓冲区记录此刻开始执行的所有写命令;
    • 3)当主服务器完成 BGSAVE 命令时,将 RDB 文件发给从服务器,从服务器接收并载入 RDB 文件,将数据库状态更新至主服务器执行 BGSAVE 命令时的数据库状态;
    • 4)主服务器将记录在缓冲区里的所有写命令发送给从服务器,从服务器执行写命令,将数据库状态更新至主服务器数据库当前的状态;

同步通信示例

  • 命令传播:同步操作完成后,主服务器会将自己执行的写命令,发送给从服务器。从服务器执行后,主从服务器数据库状态再次回到一致状态;

1.2 旧版复制功能的缺陷

  • 处于命令传播阶段的主从服务器因为网络问题中断了复制,重连后从服务器会向主服务器发送 SYNC 命令执行同步操作;

旧版复制功能的缺陷

  • SYNC 命令非常消耗资源:
    • 主服务器执行 BGSAVE 命令时:耗费主服务器大量 CPU、内存与磁盘 IO 资源;
    • 主服务器将 RDB 文件发给从服务器时:耗费主从服务器大量网络资源(流量和带宽),并对主服务器响应命令请求的时间产生影响;
    • 从服务器在载入 RDB 文件期间:从服务器阻塞无法处理命令请求;

2. 新版复制功能的实现

  • 新版指 Redis 2.8 以后版本;
  • 新版 Redis 的复制功能分为完全重同步(full resynchronization)和部分重同步(partial resynchronization)两个操作:
    • 完全重同步:与初次复制情况相同;
    • 部分重同步:主服务器仅将主从服务器在断线期间的写命令发给从服务器。使用 PSYNC 命令;

新版复制功能的实现

2.1 部分重同步的实现原理

  • 部分重同步依赖以下三个部分:
    • 主从服务器的复制偏移量(replication offset);
    • 主服务器的复制积压缓冲区(replication backlog);
    • 主服务器的运行 ID(run ID);
  • 复制偏移量
    • 执行复制的主从服务器双方会分别维护一个复制偏移量;
    • 主服务器每次向从服务器传播 N 个字节数据时,将自己的复制偏移量加 N;
    • 从服务器每次收到主服务器传播来的 N 字节数据时,将自己的复制偏移量加 N;
    • 通过对比主从服务器的复制偏移量可以判断其数据库状态是否一致;

复制偏移量示例

  • 复制积压缓冲区
    • 复制积压缓冲区由主服务器维护一个固定长度(fixed-size)先进先出(FIFO)队列,默认大小 1MB;
    • 当主服务器进行命令传播时,会将命令发给从服务器,同时将写命令入队到复制积压缓冲区;
    • 重连时,从服务器将自己的复制偏移量 offset 发给主服务器:
    • offset+1 的内容在复制积压缓冲区内,主服务器对从服务器执行部分同步操作;
    • 反之,执行完整重同步操作;
  • 复制积压缓冲区的大小可以根据公式估算:second * write_size_per_second
    • second 为从服务器断线后重连所需平均时间;
    • write_size_per_second 为主服务器平均每秒产生的写命令数据量;
    • 为安全起见,可将复制积压缓冲区大小设置为:上述公式乘 2;

复制积压缓冲区

  • 服务器运行 ID
    • 每个 Redis 都有自己的运行 ID;
    • 运行 ID 在服务器启动时自动生成,由 40 个随机的十六进制字符组成;
    • 从服务器对主服务器进行初次复制时,主服务器会将自己的运行 ID 传送给从服务器,从服务器将这个 ID 保存;
    • 重连时,从服务器向主服务器发送保存的运行 ID:
    • 若从服务器保存的 ID 为当前主服务器运行 ID,根据偏移量判断重同步方式;
    • 反之,执行完整重同步操作;

3. PSYNC 命令的实现

  • PSYNC 命令的调用方法有两种:
    • 从服务器没有复制过任何主服务器,或之前执行过 SLAVEOF NO ONE 命令,那么从服务器在开始复制时向主服务器发送 PSYNC ? -1 命令,主动请求主服务器进行完整重同步;
    • 反之,从服务器发送 PSYNC master_run_id offset 命令,master_run_id 为上一次复制时的主服务器的运行 ID,offset 为从服务器当前的复制偏移量;
  • 主服务器接受 PSYNC 命令后会产生三种回复:
    • 完整重同步:返回 +FULLRESYNC master_run_id offset,master_run_id 为当前主服务器的运行 ID,offset 为主服务器当前的复制偏移量;
    • 部分重同步:返回 +CONTINUE,从服务器只需要等待主服务器发送自己缺少的部分数据;
    • 错误:返回 -ERR,主服务器版本低于 Redis 2.8,从服务器发送 SYNC 命令,与主服务器执行完整同步操作;

PSYNC 命令的执行情况

4. 复制的详细步骤

  • 该步骤为 Redis 2.8 版本以上
  • 客户端向从服务器发送 SLAVEOF ip port 命令;

4.1 设置主服务器的地址和端口

  • 从服务器将 SLAVEOF ip port 命令的 ip 地址和 port 端口保存到服务器状态里:

    struct redisServer{
      //...
      //主服务器的地址
      char *masterhost;
      //主服务器的端口
      int masterport;
    };
  • 从服务器向客户端返回 OK,然后开始复制;

4.2 建立套接字连接

  • 从服务器根据 masterhostmasterport 创建连向主服务器的套接字连接;
  • 套接字连接(connect)成功后,从服务器为该套接字关联一个文件事务处理器,专门用来处理复制工作;
  • 主服务器接受(accept)从服务器的套接字连接后,为该套接字创建响应客户端状态,并将从服务器视作客户端(client);
  • 此时,从服务器具备服务器(server)和客户端(client)双重身份;

建立套接字连接

4.3 发送 PING 命令

  • 从服务器成为主服务器的客户端后,向主服务器发送 PING 命令;
  • PING 命令的作用:
    • 检查套接字的读写状态是否正常;
    • 检查主服务器能否正常处理命令请求;
  • 主服务器对 PING 命令的回复有 3 种情况:
    • 返回命令回复:但从服务器不能在有限时间内读取命令内容,表示主从服务器间网络连接状态不佳。此时从服务器会断开并重新创建连向主服务器的套接字;
    • 返回错误:表示主服务器暂时没法处理从服务器的命令请求。此时从服务器会断开并重新创建连向主服务器的套接字;
    • 返回 PONG:表示网络连接正常,从服务器可以继续执行复制工作;

发送 PING 命令的几种情况

4.4 身份验证

  • 从服务器收到 PONG 回复后,根据是否设置 masterauth 选项决定是否进行身份验证;
    • 从服务器没有设置 masterauth 选项,不进行身份验证;
    • 从服务器设置了 masterauth 选项,需要进行身份验证;
  • 在需要进行身份验证的情况下,从服务器给主服务器发送 AUTH password 命令,命令的参数 password 为从服务器 masterauth 选项的值;
  • 从服务器在进行身份验证时根据:主服务器的 requirepass 选项和从服务器的 masterauth 选项不同,可能遇到以下情况:

    主服务器的 requirepass 选项 从服务器的 masterauth 选项 情况
    设置 设置 相同则继续复制,不同则返回 invalid password 错误
    设置 没有设置 返回 NOAUTH 错误
    没有设置 设置 返回 no password is set 错误
    没有设置 没有设置 继续复制工作

身份验证的各种情况

4.5 发送端口信息

  • 身份验证完成后,从服务器执行 REPLCONF listening-port port-number 命令,向主服务器发送自己的监听端口号 port-number
  • 主服务器接受后,存储在从服务器的客户端状态 slave_listening_port 属性中:

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

4.6 同步

  • 从服务器向主服务器发送 PSYNC 命令,执行同步操作,将自己的数据库更新至主服务器数据库当前所处的状态;
  • 在同步操作执行完后,主服务器会成为从服务器的客户端,理由如下:
    • 完整重同步情况:需要将缓冲区里的写命令发送给从服务器;
    • 部分重同步情况:需要将复制积压缓冲区里的写命令发送给从服务器;

同步操作下主从服务器互为对方的客户端

4.7 命令传播

  • 完成同步后,主服务器进入命令传播阶段,一直将写命令发给从服务器;

5. 心跳检测

  • 在命令传播阶段,从服务器默认每秒向主服务器发送命令 REPLCONF ACK replication_offsetreplication_offset 参数是从服务器当前的复制偏移量;
  • 心跳检测的三个作用:
    • 检测主从服务器的网络连接状态:使用 INFO replication 命令可以查看从服务器最后一次向主服务器发送 REPLCONF ACK 命令距离现在过了多久,一般在 0~1 秒为正常;
    • 辅助 min-slaves 配置选项:当从服务器数量 x 少于 min-slaves-to-write 属性值或 x 个服务器的延迟大于等于 min-slaves-max-lag 属性值时,主服务器拒绝写命令;
    • 检测命令丢失:当主服务器发现从服务器的 replication_offset 参数与自己的不一致时,补发写命令数据;
  • 补发命令数据与部分重同步的区别在于:前者没有断线,后再断线了;
  • Redis 2.8 版本以前没有补发命令数据功能;

最后

::: hljs-center

新人制作,如有错误,欢迎指出,感激不尽!

:::

::: hljs-center

欢迎关注公众号,会分享一些更日常的东西!

:::

::: hljs-center

如需转载,请标注出处!

:::

::: hljs-center

公众号二维码

:::