Redis 主从复制是用于数据冗余和高可用性的关键机制。其设计与实现涉及以下几个主要方面:
-
主从角色:
- 主节点(Master) :处理写请求,并将数据同步给从节点。
- 从节点(Slave) :接收来自主节点的数据复制,通常用于读扩展或备份。
-
复制过程:
- 当一个从节点连接到主节点时,它会发送一个
SYNC命令。 - 主节点会执行一次快照保存操作(RDB持久化),并将生成的RDB文件发送给从节点。从节点接收到该文件后进行加载。
- 主节点在发送RDB文件的同时,会记录期间的新写入命令,并在RDB传输完成后,将这些增量命令发送给从节点,保证从节点的数据一致性。
- 当一个从节点连接到主节点时,它会发送一个
-
断线重连:
- 如果主从连接断开,从节点会自动尝试重新连接主节点。
- 重新连接后,如果 Redis 版本支持部分重同步(PSYNC),那么只需同步断开期间缺失的部分数据;否则需要进行全量同步。
-
心跳机制:
- 从节点会定期发送
REPLCONF ACK命令给主节点,以报告自身的偏移量。这可以帮助检测网络问题以及数据是否一致。
- 从节点会定期发送
-
读写分离:
- 在主从架构中,一般推荐将写操作集中在主节点,读操作则可以分发到从节点,提高读吞吐量。
-
故障转移:
- 虽然基本的主从复制不包括自动故障转移,但可以通过 Redis Sentinel 实现。当 Sentinel 检测到主节点宕机时,可以自动提升某个从节点为主节点并通知其他从节点更新配置。
复制详细流程
-
初次连接与全量同步:
- 当一个从节点首次连接到主节点时,它会向主节点发送
PSYNC命令。 - 如果从节点是第一次连接,或者需要的是全量同步,主节点会开始生成当前数据集的快照(RDB文件)。
- 在生成 RDB 文件期间,主节点会将所有新的写命令记录到一个积压缓冲区中。
- 主节点完成 RDB 文件后,将其发送到从节点。从节点接收并加载这个快照文件。
- 加载完成后,主节点会将积压缓冲区中的增量命令发送给从节点,使得从节点的数据状态与主节点完全一致。
- 当一个从节点首次连接到主节点时,它会向主节点发送
-
部分重同步:
- 如果从节点曾经连接过主节点,并且有一个匹配的复制偏移量和主节点ID,则可以尝试部分重同步。
- 在这种情况下,从节点会请求仅同步自上次断开后的增量数据,而不是整个数据集。
- 如果主节点的积压缓冲区中仍然保留有从节点所需的全部更新,那么部分重同步可以成功执行,否则回退到全量同步。
-
命令传播(实时同步):
- 在正常运行期间,主节点会将每个写操作命令同步发送到所有已连接的从节点。
- 从节点接收到这些命令后,立即在本地执行,以保持一致性。
-
心跳检测与故障处理:
- 为了保持主从连接的稳定,从节点定期向主节点发送心跳消息,使用
REPLCONF ACK命令来确认它们的复制偏移。 - 主节点也会周期性地发送 PING 消息,检测从节点是否在线。
- 如果主从连接中断,从节点将不断尝试重新连接,并根据具体情况选择全量或部分重同步。
- 为了保持主从连接的稳定,从节点定期向主节点发送心跳消息,使用
-
优雅切换:
- 支持手动或自动触发主从角色切换,例如通过 Redis Sentinel 实现自动故障转移。
复制积压缓冲区设计
复制积压缓冲区(Replication Backlog)是 Redis 主从复制机制中的一个关键组件,它用于支持从节点的部分重同步。以下是复制积压缓冲区的设计与实现细节:
设计概念
-
环形缓冲区:
- 积压缓冲区在 Redis 内部实现为一个固定大小的环形缓冲区。这意味着它会覆盖旧的数据来存储新数据,确保始终保持最新的一段写命令历史。
-
维护数据一致性:
- 缓冲区用于保存主节点发送给所有从节点的写命令的历史记录。如果从节点临时断开连接,且在重连时其端仍有匹配的偏移量和主节点ID,则可以使用这个缓冲区进行部分重同步。
实现原理
-
配置和大小:
- 积压缓冲区的大小可以通过
repl-backlog-size参数配置。在设置大小时,需要考虑内存消耗和多节点环境中可能的延迟。
- 积压缓冲区的大小可以通过
-
数据写入:
- 当主节点接收到写请求时,这些命令不仅执行并发送到已连接的从节点,还同时写入到积压缓冲区中。
-
偏移量管理:
- 每个命令在缓冲区中的位置由全局复制偏移量标记,该偏移量随着每次写操作递增。
- 主节点和从节点都维护各自的偏移量,从节点通过这些偏移量来判断自身与主节点之间的数据差异。
-
部分重同步:
- 当从节点重新连接后,如果它提供的偏移量在积压缓冲区的范围内,主节点只需将这些缺失的命令从缓冲区发送给从节点。
- 这种机制避免了需要进行全量同步,提高了效率,特别是在网络短暂中断或其他小故障之后。
-
内存管理:
- 由于是环形结构,当缓冲区写满后,新写入的数据会覆盖最早的数据。因此,其设计必须在大小和性能之间取得平衡,以尽量覆盖常见的网络间断导致的数据丢失场景。
数据结构
-
数组:
- 环形缓冲区通常使用一个固定大小的一维数组来存储数据。这是因为数组允许通过索引快速访问元素,且内存分配简单。
-
指针或索引:
-
缓冲区维护两个索引:
head和tail。head指向当前读取位置。tail指向下一个写入位置。
-
在某些实现中,也可能用
count来记录已用空间的大小,以帮助判断缓冲区的状态。
-
基本操作
-
初始化:
- 分配一个固定大小的数组,并将
head和tail初始化为 0。
- 分配一个固定大小的数组,并将
-
写入(enqueue/produce) :
- 将新数据写入
tail所指向的位置。 - 更新
tail索引。如果tail到达数组末尾,则循环回到起始位置(即tail = (tail + 1) % size)。
- 将新数据写入
-
读取(dequeue/consume) :
- 从
head索引读取数据。 - 更新
head索引。如果head到达数组末尾,则循环回到起始位置(即head = (head + 1) % size)。
- 从
-
空与满判断:
- 缓冲区为空:
head == tail且没有数据被写入。 - 缓冲区为满:通常判断条件为
(tail + 1) % size == head。
- 缓冲区为空:
注意事项
- 覆盖策略:当缓冲区满时,新数据可以选择覆盖旧数据(常见于日志系统),或者阻塞等待空间腾出。
- 线程安全:在多线程环境中,需要额外的机制(锁、信号量等)来确保并发访问的安全性。
- 性能:环形缓冲区通过避免数据的移动,提供 O(1) 的时间复杂度进行插入和删除操作,非常适合于实时系统和流式数据处理。
思考题1: 因为断线而处于不一致的从服务器,如何恢复后同步状态
当从节点因断线而导致数据状态不一致时,恢复后同步状态的过程主要依赖于 Redis 的复制机制。以下是具体步骤:
-
重新连接主节点:
- 从节点会自动尝试重新连接到主节点。当连接成功时,从节点会发送
PSYNC命令,请求同步。
- 从节点会自动尝试重新连接到主节点。当连接成功时,从节点会发送
-
部分重同步:
- 如果从节点的复制偏移量和主节点ID与之前保持一致,并且主节点的积压缓冲区中仍然有从节点丢失的数据,则可以进行部分重同步。这种情况下,只需要同步断开期间缺失的数据。
-
全量同步:
- 如果无法进行部分重同步(例如,主节点的积压缓冲区没有保留足够的数据),则必须执行全量同步。
- 主节点会生成当前数据库快照(RDB文件),并将其发送到从节点。从节点加载这个快照后,主节点会再发送在此期间接收到的新写命令,以完成最终的同步。
-
处理传输中的命令:
- 在全量或部分同步完成后,主节点会继续发送新接收的写操作命令给从节点,以确保它们保持最新的数据状态。
-
监控同步进度:
- 使用监控工具或 Redis 提供的命令,如
INFO replication,查看从节点的同步状态和延迟情况,确保同步过程顺利完成。
- 使用监控工具或 Redis 提供的命令,如