开发易忽视的问题:Redis主从复制实现原理

631 阅读8分钟

Redis 主从复制是用于数据冗余和高可用性的关键机制。其设计与实现涉及以下几个主要方面:

  1. 主从角色

    • 主节点(Master) :处理写请求,并将数据同步给从节点。
    • 从节点(Slave) :接收来自主节点的数据复制,通常用于读扩展或备份。
  2. 复制过程

    • 当一个从节点连接到主节点时,它会发送一个 SYNC 命令。
    • 主节点会执行一次快照保存操作(RDB持久化),并将生成的RDB文件发送给从节点。从节点接收到该文件后进行加载。
    • 主节点在发送RDB文件的同时,会记录期间的新写入命令,并在RDB传输完成后,将这些增量命令发送给从节点,保证从节点的数据一致性。
  3. 断线重连

    • 如果主从连接断开,从节点会自动尝试重新连接主节点。
    • 重新连接后,如果 Redis 版本支持部分重同步(PSYNC),那么只需同步断开期间缺失的部分数据;否则需要进行全量同步。
  4. 心跳机制

    • 从节点会定期发送 REPLCONF ACK 命令给主节点,以报告自身的偏移量。这可以帮助检测网络问题以及数据是否一致。
  5. 读写分离

    • 在主从架构中,一般推荐将写操作集中在主节点,读操作则可以分发到从节点,提高读吞吐量。
  6. 故障转移

    • 虽然基本的主从复制不包括自动故障转移,但可以通过 Redis Sentinel 实现。当 Sentinel 检测到主节点宕机时,可以自动提升某个从节点为主节点并通知其他从节点更新配置。

复制详细流程

  1. 初次连接与全量同步

    • 当一个从节点首次连接到主节点时,它会向主节点发送 PSYNC 命令。
    • 如果从节点是第一次连接,或者需要的是全量同步,主节点会开始生成当前数据集的快照(RDB文件)。
    • 在生成 RDB 文件期间,主节点会将所有新的写命令记录到一个积压缓冲区中。
    • 主节点完成 RDB 文件后,将其发送到从节点。从节点接收并加载这个快照文件。
    • 加载完成后,主节点会将积压缓冲区中的增量命令发送给从节点,使得从节点的数据状态与主节点完全一致。
  2. 部分重同步

    • 如果从节点曾经连接过主节点,并且有一个匹配的复制偏移量和主节点ID,则可以尝试部分重同步。
    • 在这种情况下,从节点会请求仅同步自上次断开后的增量数据,而不是整个数据集。
    • 如果主节点的积压缓冲区中仍然保留有从节点所需的全部更新,那么部分重同步可以成功执行,否则回退到全量同步。
  3. 命令传播(实时同步)

    • 在正常运行期间,主节点会将每个写操作命令同步发送到所有已连接的从节点。
    • 从节点接收到这些命令后,立即在本地执行,以保持一致性。
  4. 心跳检测与故障处理

    • 为了保持主从连接的稳定,从节点定期向主节点发送心跳消息,使用 REPLCONF ACK 命令来确认它们的复制偏移。
    • 主节点也会周期性地发送 PING 消息,检测从节点是否在线。
    • 如果主从连接中断,从节点将不断尝试重新连接,并根据具体情况选择全量或部分重同步。
  5. 优雅切换

    • 支持手动或自动触发主从角色切换,例如通过 Redis Sentinel 实现自动故障转移。

复制积压缓冲区设计

复制积压缓冲区(Replication Backlog)是 Redis 主从复制机制中的一个关键组件,它用于支持从节点的部分重同步。以下是复制积压缓冲区的设计与实现细节:

设计概念

  1. 环形缓冲区

    • 积压缓冲区在 Redis 内部实现为一个固定大小的环形缓冲区。这意味着它会覆盖旧的数据来存储新数据,确保始终保持最新的一段写命令历史。
  2. 维护数据一致性

    • 缓冲区用于保存主节点发送给所有从节点的写命令的历史记录。如果从节点临时断开连接,且在重连时其端仍有匹配的偏移量和主节点ID,则可以使用这个缓冲区进行部分重同步。

实现原理

  1. 配置和大小

    • 积压缓冲区的大小可以通过 repl-backlog-size 参数配置。在设置大小时,需要考虑内存消耗和多节点环境中可能的延迟。
  2. 数据写入

    • 当主节点接收到写请求时,这些命令不仅执行并发送到已连接的从节点,还同时写入到积压缓冲区中。
  3. 偏移量管理

    • 每个命令在缓冲区中的位置由全局复制偏移量标记,该偏移量随着每次写操作递增。
    • 主节点和从节点都维护各自的偏移量,从节点通过这些偏移量来判断自身与主节点之间的数据差异。
  4. 部分重同步

    • 当从节点重新连接后,如果它提供的偏移量在积压缓冲区的范围内,主节点只需将这些缺失的命令从缓冲区发送给从节点。
    • 这种机制避免了需要进行全量同步,提高了效率,特别是在网络短暂中断或其他小故障之后。
  5. 内存管理

    • 由于是环形结构,当缓冲区写满后,新写入的数据会覆盖最早的数据。因此,其设计必须在大小和性能之间取得平衡,以尽量覆盖常见的网络间断导致的数据丢失场景。

数据结构

  1. 数组

    • 环形缓冲区通常使用一个固定大小的一维数组来存储数据。这是因为数组允许通过索引快速访问元素,且内存分配简单。
  2. 指针或索引

    • 缓冲区维护两个索引:head 和 tail

      • head 指向当前读取位置。
      • tail 指向下一个写入位置。
    • 在某些实现中,也可能用 count 来记录已用空间的大小,以帮助判断缓冲区的状态。

基本操作

  1. 初始化

    • 分配一个固定大小的数组,并将 head 和 tail 初始化为 0。
  2. 写入(enqueue/produce)

    • 将新数据写入 tail 所指向的位置。
    • 更新 tail 索引。如果 tail 到达数组末尾,则循环回到起始位置(即 tail = (tail + 1) % size)。
  3. 读取(dequeue/consume)

    • 从 head 索引读取数据。
    • 更新 head 索引。如果 head 到达数组末尾,则循环回到起始位置(即 head = (head + 1) % size)。
  4. 空与满判断

    • 缓冲区为空:head == tail 且没有数据被写入。
    • 缓冲区为满:通常判断条件为 (tail + 1) % size == head

注意事项

  • 覆盖策略:当缓冲区满时,新数据可以选择覆盖旧数据(常见于日志系统),或者阻塞等待空间腾出。
  • 线程安全:在多线程环境中,需要额外的机制(锁、信号量等)来确保并发访问的安全性。
  • 性能:环形缓冲区通过避免数据的移动,提供 O(1) 的时间复杂度进行插入和删除操作,非常适合于实时系统和流式数据处理。

思考题1: 因为断线而处于不一致的从服务器,如何恢复后同步状态

当从节点因断线而导致数据状态不一致时,恢复后同步状态的过程主要依赖于 Redis 的复制机制。以下是具体步骤:

  1. 重新连接主节点

    • 从节点会自动尝试重新连接到主节点。当连接成功时,从节点会发送 PSYNC 命令,请求同步。
  2. 部分重同步

    • 如果从节点的复制偏移量和主节点ID与之前保持一致,并且主节点的积压缓冲区中仍然有从节点丢失的数据,则可以进行部分重同步。这种情况下,只需要同步断开期间缺失的数据。
  3. 全量同步

    • 如果无法进行部分重同步(例如,主节点的积压缓冲区没有保留足够的数据),则必须执行全量同步。
    • 主节点会生成当前数据库快照(RDB文件),并将其发送到从节点。从节点加载这个快照后,主节点会再发送在此期间接收到的新写命令,以完成最终的同步。
  4. 处理传输中的命令

    • 在全量或部分同步完成后,主节点会继续发送新接收的写操作命令给从节点,以确保它们保持最新的数据状态。
  5. 监控同步进度

    • 使用监控工具或 Redis 提供的命令,如 INFO replication,查看从节点的同步状态和延迟情况,确保同步过程顺利完成。