Redis 主从复制:从原理到实践的深度解析

主从复制概述

菜小弟:表哥,持久化的配置我已经学会了,但是如果服务器宕机了,虽然数据不会丢了,但是数据恢复期间,Redis提供不了服务,怎么办?,听说 Redis 可以通过主从复制来实现数据备份和读写分离,这个概念我有点懵,你能给我讲讲吗?

表哥:Redis宕机确实是很危险的,Redis设计了主从架构, 可以通过主从复制来实现数据备份和读写分离。主从复制是 Redis 中非常重要的机制,简单来说,它是指一个 Redis 实例(主库)将数据同步到一个或多个 Redis 实例(从库)的过程。

菜小弟:那主从复制的主要目的是什么呀?

表哥:主要有三个目的:

  1. 数据冗余:通过复制数据到从库,可以在主库出现问题时从从库恢复数据。
  2. 读写分离:主库负责处理写操作,从库负责处理读操作,这样可以分担主库的压力。
  3. 高可用性:如果主库挂了,可以快速将从库提升为新的主库,保证系统继续运行。

菜小弟:原来如此!那主库和从库各自负责什么任务呢?


主库负责读写,从库负责读

表哥 :在主从复制中,主库是唯一负责写操作的节点,所有写请求(比如 SETDEL 等命令)都会由主库处理。主库会将写操作同步到从库,从库则会执行相同的命令以保证数据一致性。

菜小弟:那从库只能读数据吗?

表哥 :是的,从库默认是只读的,也就是说,它只能处理读请求(比如 GET 命令)。这样做的主要好处是:

  • 减轻主库压力:可以将读请求分散到多个从库上,减轻主库的负担。
  • 避免写冲突:如果从库也允许写操作,可能会出现不同从库之间的数据不一致问题。主库作为唯一写入口,确保了数据的单一来源,避免写冲突。

菜小弟:那如果从库挂了,会影响主库吗?

表哥:不会的,主库和从库是完全独立的。从库如果挂了,主库可以继续正常工作。从库恢复后,它会重新连接主库,继续同步数据。

菜小弟:听起来很棒!那如何配置主从复制呢?


如何配置主从复制

表哥:配置主从复制很简单,主要有两种方式:通过配置文件或者命令行。下面我给你详细讲解一下。

菜小弟:好呀,我洗耳恭听!

通过配置文件配置

  • 主库配置:默认情况下,Redis 启动时是主库,不需要额外配置。
  • 从库配置:在从库的 redis.conf 文件中,找到以下配置项并修改:replicaof <master_ip> <master_port>,这里的 <master_ip> 是主库的 IP 地址,<master_port> 是主库的端口号(默认是 6379)。 如果需要主库密码验证,可以添加:masterauth
  • 启动从库:修改完配置文件后,重启 Redis 实例即可。

通过命令行配置

  • 连接到 Redis 从库 :使用 redis-cli 连接到从库的 Redis 实例。
  • 执行命令:replicaof <master_ip> <master_port>,这条命令会将当前 Redis 实例设置为从库,并连接到指定的主库。
  • 如果需要主库密码验证:CONFIG SET masterauth

菜小弟:这么简单!那配置完成后,我怎么知道主从复制是否正常工作?

表哥 :你可以使用 INFO replication 命令查看主从复制的状态。在从库上运行这个命令,会显示主库的连接状态、同步进度等信息。

菜小弟:我想知道Redis 主从复制的原理,能否帮我详细解释一下?


主从复制的原理

表哥:当然可以!Redis 主从复制的数据同步机制主要分为:全量复制和增量复制。

主从库间首次复制

菜小弟:主从库第一次同步是怎么回事啊?

表哥:当从库第一次连接主库时,从库是没有任何数据的,它需要从主库那里获取全部的数据,这个过程就是全量复制。

  1. 主从库建立连接,协商同步

    • 从库启动后,会主动向主库发送 PSYNC 命令,请求同步数据。
    • 因为是第一次同步,从库没有 runIDoffset,所以会发送 PSYNC ? -1
    • 主库收到psync命令后,返回FULLRESYNC {runId}{offset}给从库。从库收到响应后,会记录下这两个参数。
  2. 主库准备 RDB 文件

    • 主库收到 PSYNC 命令后,知道从库没有数据,就会启动一个子进程来生成一个 RDB 快照文件。
    • 这个 RDB 文件包含了主库当前所有的数据。
  3. 主库发送 RDB 文件

    • RDB 文件生成后,主库会将它传送给从库。
    • 这个过程可能会占用较多网络带宽,尤其是数据量很大的时候。
  4. 从库加载 RDB 文件

    • 从库接收到 RDB 文件后,会清空自己的所有数据,然后加载 RDB 文件。
    • 这一步完成后,从库的数据就和主库就一致了。
  5. 同步复制缓冲区的写操作

    • 在主库生成 RDB 文件的过程中,主库可能会继续处理新的写操作。
    • 这些写操作会被记录到 replication buffer 中。
    • RDB 文件发送完成后,主库会将这些写操作发送给从库,从库执行这些操作,确保数据完全同步。

菜小弟:哦,我明白了!全量复制就是从库第一次连接主库时,主库把自己的所有数据复制给从库,对吧?

表哥:没错!第一次同步就是全量复制,因为从库没有任何数据,需要从头开始同步。

菜小弟:那第一次同步过后,是不是主从库就可以一直同步了?

表哥 :主从完成了全量同步 后,主从连接保持长连接,主库为每个从库维护一个复制客户端连接 。客户端向主库发送写命令(如SET name redis)。主库执行写命令后,将命令以二进制数据流的形式发送给所有从库。

菜小弟:那发送这么多从库不会影响主库的性能吗?

表哥:主库不会等待从库ACK确认,继续处理其他客户端请求(这是Redis默认的异步复制机制)。

菜小弟:了解了表哥,你上面说的runID和offset都是什么意思能讲一下吗?

表哥 :好的,runIDoffset 是 Redis 主从复制中非常重要的两个概念,它们用于标识主库的唯一性和同步进度。下面我们详细解析这两个概念。


runIDoffset 详解

  1. runID 是什么

    定义

    runID 是 Redis 实例的唯一标识符,每个 Redis 实例在启动时都会生成一个随机的 runIDrunID 是一个长度为 40 的十六进制字符串。

    作用

    • 标识主库runID 用于标识当前主库的唯一性。当从库与主库建立连接时,主库会将自己的 runID 发送给从库。
    • 识别主库变化 :如果主库发生了切换(比如通过 replicaof 命令修改主库),从库可以通过 runID 判断当前主库是否与之前的主库一致。

    示例

    假设主库的 runIDabc123def456ghi789jkl012mno345pqr678stu901,从库在同步时会记录这个值。如果主库发生了切换,从库会发现新的主库 runID 与之前记录的不一致,从而触发全量同步。如果是第一次同步,runID?offset-1。如果是重新连接,runIDoffset 是从库之前记录的。

  2. offset 是什么

    定义

    offset 是一个偏移量,表示从库当前同步到主库的哪个位置。它记录的是从库在 replication stream 中的位置。

    作用

    • 同步进度offset 用于表示从库已经接收并执行了多少主库的写操作。
    • 断点续传 :当从库与主库的连接断开后,重新连接时,从库会将自己的 offset 发送给主库。主库会根据 offset 判断是否需要发送增量数据。

    示例

    假设主库的写操作序列是 [SET a 1, SET b 2, SET c 3],如果从库的 offset 是 2,表示从库已经接收并执行了前两个写操作(SET a 1SET b 2),下一个需要同步的操作是 SET c 3

菜小弟offset 就是从库同步的进度条,对吧?

表哥 :非常形象!offset 确实就是一个进度条,告诉我们从库已经同步到哪里了。


增量同步

菜小弟:那如果网络波动导致命令丢失怎么办?比如主库发了SET命令但从库没收到的情形?

表哥:这个问题问的很好,这也是下面我要给你讲的增量同步。看一下这张图:

主从连接断开,从库尝试重连
  1. 断开原因:网络波动、主库宕机、防火墙配置变化等导致主从连接中断。
  2. 从库心跳检测:从库会定时(repl-ping-slave-period 参数控制,默认10秒)发送心跳(REPLCONF ACK)测试连接。若多次失败(由repl-timeout控制,默认60秒),则判定主从连接断开。
  3. 等待重连期:从库会持续尝试重新连接主库(通过replicaof配置的IP和端口)。
主从重连后,检查是否触发全量复制
  1. 从库发送 PSYNC 命令 :携带runidoffset(记录自己旧的复制偏移量)。

    • runid:主库每次启动时生成的唯一标识,用于标记数据版本。若主库重启或发生故障切换,该ID会刷新。
    • offset:从库记录自己上次同步到的数据偏移量。
  2. 主库判断能否增量同步

    • 如果从库的runid与当前主库一致,且旧的offset还在主库的复制积压缓冲区(repl_backlog_buffer) 范围内,主库返回+CONTINUE则触发增量复制。
    • 否则offset对应的数据已不在缓冲区内),主库回复+FULLRESYNC <runid> <offset>,触发全量复制
主库发送增量数据

假设验证通过,主库会把积压缓冲区中从offset+1到当前的数据,通过字节流方式发送给从库。

菜小弟:如果缓冲区里有数据但断线时间太久,是不是会部分覆盖?

表哥 :对的!比如缓冲区存了10MB数据(2000字节),但从库的offset落后了3000字节,这3000-2000=1000字节的数据已经被覆盖,就只能全量复制了。这也说明积压缓冲区越大,能容忍的断线时间越久!

从库追赶数据
  1. 重放命令:从库收到增量数据后,按顺序执行这些命令,同时更新自己的offset。
  2. 追赶主库 :最终从库会补完所有缺失的命令,直到offset和主库的master_repl_offset一致。

菜小弟:这些增量命令是Redis命令吗?比如Set、HSet?

表哥 :不全是!缓冲区存的是协议格式的原始字节,类似主库发往从库的字节流。比如*3\r\n$3\r\nSET\r\n$4\r\nname\r\n$5\r\nredis\r\n这种形式。所以传输效率比存实际命令更高!

菜小弟:表哥,你刚才说的复制积压缓冲区(repl_backlog_buffer)是什么东西,它和replication buffer又有什么关系?


Repl_backlog_buffer 与 Replication Buffer 的详解

repl_backlog_buffer(复制积压缓冲区)

定义与作用

  • 全局环形缓冲区 ,由主节点维护,所有从节点都共同使用这一个缓冲区,保存近期的写命令副本,用于支持增量复制
  • 所有从节点共享此缓冲区,当从节点断开重连时,通过比较偏移量判断是否需要全量同步。

核心特性

  • 环形结构 :固定大小(通过 repl-backlog-size 配置),覆盖旧数据时采取循环写入策略。
  • 偏移量追踪 :主节点记录 master_repl_offset,从节点记录 slave_repl_offset
  • 容灾恢复 :若从节点的 slave_repl_offset 仍在缓冲区内,触发增量同步;否则需全量同步。

配置优化

  • 建议大小 :至少为主库每秒写入量 × 最大允许断线时间 ,例如高峰期每秒 10MB,允许断线 60 秒,则设 600MB,并预留 1.5 倍余量。
  • 修改配置需谨慎,调整 repl-backlog-size 会重置缓冲区,可能触发全量同步。

示例场景 : 主库每秒处理 1 万次写操作,repl-backlog-size 设为 100MB。此时允许从库断线约 10 秒(100MB / 10MB/s = 10s),超过这个时间将从缓冲区溢出,必须全量同步。

replication buffer(复制客户端缓冲区)

定义与作用

  • 每个从节点的独立缓冲区 ,属于主节点与特定从节点连接的客户端输出缓冲区。也就是说主节点上维护了与每一个从节点的缓冲区,比如有3个从节点主库就会维护三个缓冲区。
  • 用于临时存放待发送给该从节点的数据(包括全量 RDB 文件或增量命令流)。

核心特性

  • 数据分类:

    • 全量复制阶段:暂存 RDB 文件的二进制流。
    • 增量复制阶段:暂存实时传播的写命令。
  • 动态管理 :受 client-output-buffer-limit replica 配置限制,超过阈值可能中断连接。

  • 异步传输:主节点将数据写入缓冲区后立即返回,由操作系统异步发送到从节点。

配置优化

  • 关键参数:

    arduino 复制代码
    client-output-buffer-limit replica 256mb 64mb 60
    • 硬限制(256MB):超过此值则关闭连接。
    • 软限制(64MB + 60秒):持续 60 秒超过 64MB 也关闭连接。
  • 全量复制期间需调大此值,避免 RDB 文件过大导致溢出。

示例场景 : 从库因网络延迟处理缓慢,主库写入速度 50MB/s,若 replication buffer 限制为 100MB,则 2 秒内未处理完会导致缓冲区溢出,主库断开连接,需重新全量同步。


菜小弟:表哥,在 Redis 主库同时处理多个从库全量复制的场景下,大量 RDB 文件的生成和传输会占用主库的 CPU、内存和网络资源,可能导致主库性能下降甚至服务中断,该怎么办?

表哥:可以使用树形复制。

树形复制(级联复制)

从库(Slave)可作为其他从库的主库,形成树形复制结构:

scss 复制代码
Master → Slave1 (同时作为二级主库) → Slave1-1, Slave1-2  
        ↘ Slave2 (同时作为二级主库) → Slave2-1, Slave2-2
  • 优点:

    • 主库只需要直接服务少量一级从库(如 Slave1/Slave2)的全量复制请求。
    • 二级从库的全量复制由一级从库承担,分散主库压力。

分片集群模式(Redis Cluster)

  • 方案: 将数据分散到多个主库(分片节点),每个主库仅服务部分数据和对应的从库。

  • 优点:

    • 全量复制的负载分散到多个主节点,避免单点瓶颈。
    • 天然支持水平扩展。
  • 适用场景: 数据量极大(如 TB 级)且需要横向扩展的场景。

总结

主从复制的核心机制

  • 全量复制:主库生成 RDB 快照并传输给从库,从库加载数据后进入增量同步。

    • 触发条件 :初次连接、从库偏移量落后超出 repl_backlog_buffer 范围。
  • 增量复制:通过repl_backlog_buffer环形缓冲区,主库发送断线期间的缺失命令。

    • 本质:基于写命令流的断点续传机制。

关键性能挑战

  • 主库资源争用:多个从库同时全量复制时,主库的 CPU、内存、磁盘 I/O、网络带宽易成瓶颈。

  • 同步延迟:从库处理速度不足导致复制积压,可能引发数据不一致。

  • 缓冲区溢出:

    • repl_backlog_buffer 溢出 → 增量复制失败 → 全量复制。
    • replication buffer 溢出 → 主从连接中断 → 全量复制重试。

参考资料:

  1. Redis设计与实现

  2. Redis核心技术与实战

相关推荐
无问8172 分钟前
SpringBoot配置文件
java·spring boot·后端
uhakadotcom4 分钟前
阿里云DataFrame入门:PyODPS和MaxFrame的使用指南
后端·面试·github
uhakadotcom8 分钟前
云服务对比:腾讯云、阿里云和火山引擎的独特优势
后端·面试·github
无眠_12 分钟前
Spring Boot 核心知识点精讲:助你快速上手与深度理解
运维·spring boot·后端
uhakadotcom16 分钟前
了解Scikit-learn:Python机器学习的强大工具
后端·面试·github
uhakadotcom19 分钟前
XGBoost入门:强大的机器学习库
后端·面试·github
阿梦Anmory20 分钟前
【spring boot 实现图片验证码 前后端】
java·spring boot·后端
Asthenia041222 分钟前
类加载器与双亲委派模型:一场家庭派对的规矩与叛逆(打破双亲委派的JDBC/Tomcat)
后端
uhakadotcom31 分钟前
阿里云Object Table:非结构化数据处理的强大工具
后端·面试·github
uhakadotcom35 分钟前
简单理解 MaxFrame 中的用户定义函数(UDF)和资源库导入
后端·面试·github