Redis主从复制原理与实战:全量同步、增量同步及故障排查
1. 主从复制到底解决了什么问题先说结论Redis 主从复制是 Redis 高可用架构的基石也是你从“会用 Redis”走向“懂 Redis”必须迈过的一道坎。我在一线摸爬滚打这些年见过太多团队把 Redis 当成一个单机缓存来用等某天 Redis 进程突然挂掉或者服务器磁盘损坏缓存瞬间全丢数据库直接被压垮这时候才想起来做持久化、做主从。实际上主从复制解决的不只是“数据备份”这一件事它至少覆盖了三个核心痛点第一数据冗余与容灾。主节点Master上的数据实时同步到从节点Slave主节点挂了从节点可以顶上继续服务数据不丢或者尽量少丢。第二读写分离。主节点负责写从节点负责读把读流量分摊到多个节点上热点数据的读压力不再集中在单机。第三为哨兵Sentinel和集群Cluster模式打基础。没有主从复制哨兵无从感知主节点状态集群的分片数据也无处安放。这篇文章我会把主从复制的底层同步逻辑、完整的搭建过程和实际运维中踩过的坑全部展开讲清楚适合刚接触 Redis 复制的初学者也适合被“全量同步、增量同步、复制偏移量、积压缓冲区”这些概念绕晕的进阶开发者。2. 复制机制的三个核心逻辑阶段2.1 旧版 SYNC 同步一把梭式的全量复制了解主从复制得先从历史版本看起。Redis 2.8 之前从节点执行SYNC命令进行复制主节点收到命令后会执行BGSAVE生成 RDB 快照文件然后把 RDB 文件发给从节点从节点清空老数据加载 RDB 完成同步。这种方案的缺陷非常明显只要主从之间的网络发生抖动、短暂断开重连后就会再次触发全量复制。如果数据量是 10GB一次全量复制要传 10GB 的数据断线重连再来一次主节点还得反复fork子进程做BGSAVECPU 和磁盘 IO 都被打满。这就像搬家的时候不管你的行李箱落在半路上了还是临时走错了一层楼每次都把整个房子重新搬一遍。这个方案在数据量大、网络不稳定的场景下基本不可用。2.2 新版 PSYNC 同步全量 增量分开走Redis 2.8 引入PSYNC命令核心思路是能只传增量就别全量重传。从节点断开重连后先尝试跟主节点协商告诉主节点自己在断开之前已经收到过哪个数据偏移量主节点判断这段时间的增量数据是否仍然在自己的积压缓冲区Replication Backlog中。如果增量数据还在缓冲区里就只发送这部分增量数据如果缓冲区已经滚动覆盖了或者从节点状态不对才降级为全量同步。这里有个关键点增量同步并不是指“只同步新产生的写命令”而是指“断线期间缺失的那部分写命令”。正常运行时主节点每执行一条写命令都会实时推送给从节点从节点执行同样的命令来保持状态一致这个实时推送的过程本身也是增量同步只是通常大家不这么叫习惯上把断线重连后的补齐机制叫增量同步。2.3 新版本 PSYNC2主从切换也能增量续传Redis 4.0 进一步优化引入PSYNC2机制。旧版 PSYNC 有个比较尴尬的场景主从发生切换后新的从节点去复制新的主节点由于复制历史 ID 发生了变化往往被迫做全量同步。PSYNC2 通过复制 IDReplication ID和偏移量的联合判断使得主从切换后新主节点的从节点也能尽量复用复制历史实现增量续传。这个改进在故障转移场景下价值极大。想象一个 50GB 的主节点挂了哨兵把一个从节点提升为新主节点此时如果其他从节点都做全量复制那新主节点要先fork子进程BGSAVE再同时向多个从节点传输 RDB瞬间可能把新主节点拖垮。有了 PSYNC2部分从节点可以直接增量追赶压力小很多。3. 一次完整的主从同步到底经历了什么3.1 从零开始的第一次握手我把一次从零开始的主从同步拆成四步尽量说得偏底层一些这样你排查问题的时候才知道该看哪里。第一步从节点向主节点发起连接。从节点配置里指定了主节点的 IP 和端口或者运行了REPLICAOF host port命令从节点会向主节点建立一个 TCP 连接然后发送PING确认主节点存活再发送带有自身监听端口、PID、复制状态等信息的请求。第二步身份认证。如果主节点配置了requirepass从节点需要提供masterauth的密码。这个环节很多人踩坑主节点配了密码从节点没配masterauth日志里反复报NOAUTH Authentication required。还有一种情况是主从都配了密码但密码不一致同样连不上但报错信息可能没那么直观需要看主节点日志才能发现。第三步全量同步的发起。从节点发送PSYNC ? -1表示自己没有任何复制历史请求全量同步。主节点收到后启动后台BGSAVE生成 RDB 快照同时把生成 RDB 期间新执行的写命令写入复制积压缓冲区。RDB 生成完成后主节点将 RDB 文件发送给从节点。这里注意如果是磁盘型复制RDB 是从磁盘读出来的如果开启了无盘复制repl-diskless-sync yesRDB 会直接从 socket 管道流式传输给从节点不走磁盘。第四步从节点加载 RDB 并追赶增量。从节点收到完整的 RDB 文件后先清空自己的旧数据然后加载 RDB 恢复到主节点快照时刻的状态。加载完成后从节点会向主节点回复ACK主节点再把积压缓冲区中缓存的增量写命令发送给从节点从节点执行这些命令数据最终对齐。3.2 复制积压缓冲区怎么算大小复制积压缓冲区Replication Backlog是个环形缓冲区默认大小 1MB由主节点维护保存主节点最近执行的写命令。它的大小直接决定了断线重连时能否增量续传。如果从节点断线超过缓冲区能覆盖的时间窗口那对不起只能全量同步。这个大小怎么估算核心公式是积压缓冲区大小 主节点每秒写入的命令字节数 × 从节点可能断线的最长时间。举例来说如果业务高峰期每秒产生约 2000 条写命令每条命令平均 200 字节那么每秒产生的数据量约为 400KB。如果希望容忍从节点断线 5 分钟缓冲区大小就需要 400KB × 300 秒 120MB。实际配置中我建议留出余量比如业务峰值翻倍考虑设置 256MB 甚至 512MB。因为一旦估算不足从节点断线时间稍微长一点就会触发全量同步那个代价远比一个缓冲区内存大得多。3.3 从节点视角的数据流向从节点收到主节点的命令后并不是直接执行完了事。它会把接收到的数据先写入自己的复制积压缓冲区从节点也有同时更新自己的复制偏移量然后才执行命令更新内存数据。这里有个细节从节点默认是只读的slave-read-only yes新版本叫replica-read-only yes。如果你不小心去从节点上写了一个 key这个写入不会同步回主节点而且主节点后续对该 key 的更新会直接把从节点上的“脏数据”覆盖掉。这个行为不是报错而是静默覆盖排查起来特别费劲。4. 动手搭建一套主从复制环境4.1 基于 Docker 快速部署两个 Redis 实例这里我用 Docker 方式演示因为干净、可重复、不用污染宿主机环境。如果你用的是本机安装的 Redis思路完全一致只是不需要容器网络配置。先创建自定义网络方便两个容器用容器名互相访问docker network create redis-repl-net启动主节点监听 6379 端口开启密码验证生产环境必备docker run -d --name redis-master \ --network redis-repl-net \ -p 16379:6379 \ redis:7.0 \ redis-server --requirepass masterpass --appendonly yes启动从节点通过--slaveof参数新版本是--replicaof指定主节点地址和端口docker run -d --name redis-slave \ --network redis-repl-net \ -p 16380:6379 \ redis:7.0 \ redis-server --slaveof redis-master 6379 --masterauth masterpass --appendonly yes这里注意几个端口映射的细节主节点的 6379 映射到宿主机 16379从节点的 6379 映射到宿主机 16380这样本机两个 Redis 实例可以通过不同端口访问互不冲突。容器内部从节点通过容器名redis-master访问主节点走的是 Docker 网络内部通信。启动完成后进入主节点查看复制状态docker exec -it redis-master redis-cli -a masterpass INFO replication在输出里你会看到role:master connected_slaves:1 slave0:ip172.x.x.x,port6379,stateonline,offsetxxx,lag1这说明从节点已经在线复制正常。4.2 从零验证全量与增量同步过程验证全量同步很简单。先给主节点灌一批数据docker exec -it redis-master redis-cli -a masterpass MSET key1 value1 key2 value2 key3 value3然后去从节点查看docker exec -it redis-slave redis-cli -a masterpass MGET key1 key2 key3如果能看到对应值说明全量同步生效。此时在主节点继续写入新数据 SET keystream hello redis replication在从节点上马上执行GET keystream能立刻看到新数据说明增量命令是实时推送的。验证断线重连后的增量续传可以直接停掉从节点容器等主节点写入一批数据后再启动从节点docker stop redis-slave docker exec -it redis-master redis-cli -a masterpass MSET gap1 val1 gap2 val2 gap3 val3 docker start redis-slave启动后观察主节点的日志如果看到类似Synchronization with replica succeeded且没有全量 RDB 传输记录说明走的是增量同步。这里的关键就是看日志里是否有Full resync字样有就是全量没有就是增量。4.3 生产环境配置文件模板Docker 演示适合学习生产环境一般用配置文件管理。我给一个最小可用的redis.conf模板标注清楚每个关键参数# 主节点配置 bind 0.0.0.0 port 6379 requirepass masterpass masterauth masterpass appendonly yes appendfsync everysec # 复制积压缓冲区大小根据写入量和断线容忍时间调整 repl-backlog-size 256mb # 从节点可接受的最大延迟超过后哨兵认为主观下线 repl-timeout 60 # 禁止主节点在只有一个从节点时关闭持久化否则做全量同步时数据容易丢 # 实际场景建议主节点必须开启 AOF# 从节点配置 replicaof master-host 6379 masterauth masterpass replica-read-only yes # 从节点写磁盘策略数据安全性要求高则与主节点保持一致 appendonly yes appendfsync everysec从节点的replicaof可以写在配置文件里也可以用命令临时设置。命令方式的好处是方便测试坏处是重启后失效。线上最好写配置文件否则节点重启后容易忘记重新设置主从关系导致数据服务异常。5. 数据同步的底层协议细节5.1 复制 ID 和偏移量是怎么配合的每个 Redis 实例在启动时会生成一个 40 位十六进制的复制 IDReplication ID主节点还有一个复制偏移量。从节点连接主节点后会把主节点的复制 ID 和偏移量记录下来。为什么要搞复制 ID因为偏移量本身不能唯一标识数据流。比如主节点重启RDB 可能恢复到历史某个时间点偏移量回退了但从节点手里的旧偏移量主节点已经不认了这时候如果只看偏移量会同步出错误的数据。复制 ID 相当于“数据流的版本号”ID 变了说明数据流发生了断裂必须全量重传。理解这个机制后你就能看懂INFO replication里输出的字段含义了。主节点上输出的master_replid和master_repl_offset从节点的master_replid和slave_repl_offset。从节点的slave_repl_offset如果一直小于master_repl_offset说明有数据延迟还没追上。5.2 命令传播的底层形式全量同步完成后主从之间维持一条长连接主节点每执行一条写命令都会把命令写入输出缓冲区然后通过这条连接发送给从节点。Redis 的复制传播使用的是 Redis Serialization ProtocolRESP格式也就是直接把命令和参数序列化后传输。主节点并不是把命令发给从节点同时就更新自己的偏移量而是先写入复制积压缓冲区再发送给每个从节点从节点确认收到后返回 ACK主节点收到 ACK 后更新该从节点的复制偏移量。网络抖动时从节点 ACK 超时主节点会把该从节点标记为断线然后尝试重连重连后再按前面讲的 PSYNC 机制进行恢复。5.3 从节点的三种状态模型从节点的复制状态模型是排查一切复制问题的地图。它大致经历这样几个状态SYNC_WAIT从节点已连接主节点等待同步开始。SYNC_FULL正在接收全量 RDB 数据。SYNC_PARTIAL正在接收增量数据断线续传场景下常见。SYNC_DONE同步完成持续接收命令传播。在INFO replication里你未必能直接看到这些状态名但可以通过master_link_status:up/down和slave_repl_offset的变化来判断。master_link_status:down表示从节点和主节点的连接已经断开常见原因有网络分区、主节点超时、主节点主动拒绝从节点连接。6. 常见问题与排查技巧实录6.1 从节点一直处于全量同步状态现象主节点日志里反复出现Full resync从节点数据始终赶不上。排查思路检查 RDB 文件大小。如果数据量几十 GB全量同步本身就需要较长时间耐心观察一段时间。如果一直不结束看主节点的INFO persistence确认BGSAVE是否反复执行。检查网络带宽。在主节点上执行docker exec redis-master redis-cli INFO stats关注total_net_output_bytes的变化速率。如果输出速率远低于网卡带宽很可能是带宽瓶颈或者跨机房专线跑满了。检查主节点的 fork 耗时。INFO stats里latest_fork_usec如果达到几十毫秒甚至几百毫秒说明 fork 子进程期间主节点进入短暂的阻塞状态。单机内存太大时fork 耗时可能造成明显的请求延迟。实际案例有一次线上从节点一直全量同步不成功查了半天发现是主节点开启了 AOF 重写定时任务BGSAVE和 AOF 重写同时进行磁盘 IO 全部被打满RDB 文件生成速度极慢。解决方案是错开 AOF 重写和全量同步的时间窗口。6.2 主从延迟突然飙升主从延迟是读写分离架构必须持续监控的指标。判断延迟的办法是分别连接主从节点执行INFO replication对比主节点的master_repl_offset和从节点的slave_repl_offset差值越大延迟越大。导致延迟飙升的常见原因主节点写命令过于密集从节点单线程执行命令的速度跟不上生产速度。这种场景下从节点的 CPU 通常是瓶颈需要检查从节点的 CPU 使用率。主节点执行的是大 key 批量操作比如SUNIONSTORE、SINTERSTORE、DEL一个几百万元素的集合同步到从节点执行时同样耗时。此时从节点会短暂阻塞延迟瞬间拉高。主从之间网络拥塞TCP 窗口调整不过来导致命令排队。处理办法在从节点上执行REPLICAOF NO ONE临时断掉同步等主节点大 key 操作完成后再重新REPLICAOF恢复但注意这样会丢失断连期间的数据变化需要评估业务接受度。6.3 主从数据不一致怎么办从节点数据跟主节点不一致首先判断是否是脏写入也就是有人在从节点上直接写了数据。可以用redis-cli --scan --pattern *遍历对比样例或者直接比对热点 key 的值。排查完脏写入后检查主从节点的持久化配置是否一致。一种诡异场景是主节点 AOF 关闭直接从容器恢复或拷贝文件恢复后内存数据和 RDB 恢复的数据不一致接着从节点同步过来的数据自然不一致。这种场景没有太好的自动修复方案只能重建同步在从节点上执行REPLICAOF NO ONE清空数据再重新REPLICAOF master-port触发全量同步。6.4 复制风暴怎么避免复制风暴指的是主节点同时向多个从节点推送全量 RDB导致主节点网络出口带宽耗尽服务整体变慢。常见于很多从节点同时断线重连或者新接入大量从节点。避免措施有三类控制单个主节点的从节点数量建议不超过 5 个。使用树形复制结构主节点先同步给若干个中间从节点这些中间从节点再作为次级主节点同步给更下层的从节点。Redis 支持从节点开启replica-read-only no后作为级联主节点继续复制但这种方式要小心配置容易绕晕。调大repl-backlog-size尽量让从节点走增量同步而不是全量同步。6.5 主节点挂了从节点自动升级默认情况下主节点挂了从节点不会自动接替主节点。这就是哨兵Sentinel存在的意义。哨兵是独立于 Redis 主从进程的一套监控程序负责监控主节点健康状态主节点不可达时通过投票机制选出一个从节点执行REPLICAOF NO ONE提升为新主节点其他从节点重新指向新主节点客户端也通过哨兵感知新主节点地址。这里要强调一个经验配置哨兵时quorum参数指的是最少几个哨兵同意主节点下线才执行故障转移。建议至少配置 3 个哨兵实例quorum设置为 2避免单点哨兵误判导致不必要的切换。同时哨兵本身也要考虑高可用不能只跑一个进程。7. 进阶实践与配置调优7.1 无盘复制到底适合什么场景Redis 默认的全量复制流程是主节点BGSAVE生成 RDB 文件到磁盘再从磁盘读取发送给从节点。这里存在两个耗时环节写盘和读盘。大内存实例下RDB 文件十几 GB写盘再读盘整个过程可能持续几十秒甚至几分钟。repl-diskless-sync yes开启后主节点fork出子进程后直接将 RDB 数据流式写入 TCP socket 发送给从节点省掉磁盘读写环节。这个参数适合磁盘性能较差、网络带宽充裕的场景。但如果网络带宽不稳定传输中断需要重传反而没有磁盘缓存可靠。实际配置中我会这么建议千兆网卡以上、磁盘是普通机械硬盘时开启无盘复制收益明显如果是 NVMe 固态硬盘磁盘读写不是瓶颈开不开区别不大。7.2 全量同步期间的写入延迟优化主节点执行BGSAVE期间因为要fork子进程会短暂阻塞主服务。阻塞时间跟内存大小成正比实测 10GB 内存的实例fork 耗时通常在几十毫秒左右20GB 以上可能达到百毫秒级。优化思路把 RDB 快照目录或者 AOF 文件放到独立的磁盘上避免和 Redis 数据目录争抢 IO错开多个从节点接入主节点的时间避免同时触发多次全量同步尽量让fork操作发生在业务低峰期。7.3 从节点的持久化策略怎么定从节点的持久化被很多人忽略认为从节点只是临时备份。这个观点在 Redis 高可用架构里是致命的。从节点的数据如果持久化关闭一旦从节点重启内存数据直接清空会重新向主节点发起全量同步。如果此时主节点数据量大等于白白增加主节点负担。更严重的情况是主节点和从节点同时重启主节点因为持久化没开启丢失了所有数据从节点同步过来的自然也是空数据。我的建议主节点和从节点都必须开启 AOF 追加持久化且appendfsync设置为everysec。这样即使在极端场景下丢失数据也只是最近 1 秒内的写操作业务可接受范围内。主节点和从节点的持久化策略要保持一致避免出现数据恢复后的差异。8. 再聊几个实操中的冷门细节在验证主从复制时经常会忽略一些看似不起眼但实际影响很大的细节我这里集中补充一下。第一个是关于masterauth的配置位置。不少人把masterauth配在从节点的requirepass下面认为同一个密码就能搞定。实际上requirepass是控制“谁可以连接当前节点”masterauth是控制“当前节点以什么身份密码去连接主节点”。两个完全独立的配置项漏掉任何一个都会导致认证失败。第二个是关于连接信息不更新的问题。如果主节点的 IP 或端口变了配置文件里还是旧地址从节点重连后无法找到主节点只能干瞪眼。建议运维侧做好主节点信息的配置管理该用环境变量注入的用环境变量不要硬编码 IP。第三个是复制偏移量的回退。主节点如果使用 RDB 重启master_repl_offset会回退到 RDB 保存时的偏移量此时从节点的slave_repl_offset高于主节点就会触发全量重同步。这也是为什么主节点单纯开 RDB 持久化并不够安全AOF 的追加日志能保证重启后偏移量尽量不丢失。第四个是关于局域网环境下的网络配置。容器部署时从节点访问主节点用的是容器内网 IP这个 IP 在 Docker 里通常是私有的重启后会变化。所以不要在对端防火墙白名单里写死 IP尽量让节点之间通过服务名或者 VIP 访问。9. 主从复制的性能指标怎么监控不管你用什么监控体系这几个指标必须盯住。第一个是INFO replication里的master_repl_offset与从节点slave_repl_offset的差值。我习惯写一个简单的脚本每分钟采集一次差值超过 10MB 持续五分钟就告警。这个阈值需要根据业务写入量调整写入量大的场景 10MB 可能在几秒内就产生误报严重要按实际情况放宽。第二个是connected_slaves。这个值应该保持稳定如果频繁在 0 和正常值之间跳变说明有从节点反复断线重连。这时候要去看从节点日志通常能看到MASTER - REPLICA sync started反复出现。第三个是主节点的INFO stats里的sync_full和sync_partial_ok计数器。如果sync_full不断增加说明频繁发生全量同步这是复制健康度恶化的强烈信号需要立即调查。第四个是主节点的repl_backlog_active和repl_backlog_size参数。确认积压缓冲区处于激活状态并且没有因为空间不足而频繁触发全量同步。可以用INFO replication查看repl_backlog_histlen字段这个值表示积压缓冲区当前包含的有效字节数。如果它长期等于repl-backlog-size设置的大小说明缓冲区经常被写满断线重连大概率会触发全量同步这时候就该调大缓冲区了。我把这些指标整理成表方便你直接抄去用指标来源核心字段健康阈值建议异常判断INFO replicationmaster_repl_offset / slave_repl_offset差值接近 0 且稳定差值持续增大表示主从延迟INFO replicationconnected_slaves与预期从节点数一致频繁波动说明连接不稳定INFO statssync_full / sync_partial_ok全量同步次数极少sync_full 持续增长需排查INFO replicationrepl_backlog_histlen明显小于 repl-backlog-size长期等于上限说明可能频繁全量同步INFO persistencerdb_last_bgsave_statusok非 ok 说明全量同步期间 BGSAVE 失败10. 个人实践中总结的几条铁律主从复制这套机制我前后踩过的坑没有十次也有八次了有些教训值得反复强调。第一主节点别关持久化。主从复制不是数据安全的兜底。很多人以为有从节点兜底就可以在主节点关闭 AOF节省磁盘 IO但主从节点同时宕机的概率并不低。如果主节点关持久化故障恢复后整个集群数据全部丢失那时候你才会明白什么叫欲哭无泪。第二从节点不要读写混杂。从节点开启了写权限后数据分叉问题几乎无法根治。有些业务为了低延迟贪图方便直接写从节点查询代码同一个 key 又去读主节点最后对不上账排查半天都找不出原因。Redis 从节点就该专心承担读流量。第三积压缓冲区宁可大不可小。内存贵但远没有全量同步的代价大。一次全量同步在大集群里可能持续几分钟期间主节点网络出口被占满正常业务请求的延迟都会受影响。花几百 MB 内存换业务稳定这笔账怎么算都划算。第四复制延迟的监控必须做实时告警。主从延迟是渐进的故障往往等到业务侧发现读不到最新数据时延迟已经积累了十几分钟。业务高峰期如果主从延迟超过 30 秒读写分离的体验基本就崩了必须及时干预。第五配置变更后一定重启验证。改配置文件最危险的情况是配置没生效你以为主从关系已经是新的了实际上老进程还在用旧配置跑。改完配置后强制重启节点并立刻执行INFO replication确认复制方向和偏移量正确再做数据验证。