Redis主从复制原理与生产实战:从同步机制到故障排查
发布时间:2026/9/30 10:59:19来源:尧图网络
从「Redis主从复制」这个词展开我第一反应不是背诵那套面试八股而是这些年踩过的坑比如从节点数据延迟导致线上读到旧数据比如没配好masterauth导致复制握手失败再比如repl_backlog太小导致从节点断线重连后被迫全量同步、主节点CPU直接飙到80%。这些问题看似各不相同根子其实都在主从复制的机制细节里。作为长期用Redis扛读写分离和数据容灾的人我建议团队里每个接触Redis的新人都亲手搭一套一主两从环境把INFO replication那段输出逐行看懂这比刷十道面试题都管用。这篇内容就按我自己的理解来写先讲主从复制到底解决了什么问题再把全量同步、增量同步的原理拆到内核级别接着给一套可以直接抄作业的配置方案包括Docker部署最后把高频故障和排查思路整理成速查表。目标是让基础一般的同学也能跟着配起来同时让有经验的同行看到一些有价值的细节。1. 主从复制到底解决了什么问题1.1 三个真实场景让你看懂主从复制的价值先说一个最常见的场景业务做促销活动某个商品详情页的Redis热点缓存QPS冲到十几万单节点Redis读性能打满CPU长时间跑在90%以上。这时候如果你只会开多级缓存或者扛着扩容成本很高。更合理的做法是把Redis改成主从结构主节点只负责写从节点分担读流量这样读能力可以水平拓展。主从复制就是给这种架构提供数据通道的核心机制。第二个场景是数据容灾。线上单节点Redis如果宕机内存里的数据全部丢失即使有RDB和AOF持久化恢复也需要时间。但如果有一台从节点实时同步主节点的数据主节点宕机后可以直接把读流量切到从节点虽然从节点此时不能立刻提供写入需要配合哨兵升级为主节点但至少业务不是全挂的状态。主从复制在这里扮演的是「数据热备份」的角色。第三个场景和日常运维直接相关。比如你有一个大Key需要做数据分析或者要给测试环境刷一份接近生产的数据直接从主节点导出会影响线上性能但连上从节点执行BGSAVE或者跑SCAN就不会干扰主节点。这也是主从复制在运维侧非常实用的一个点。这三个场景归结起来就是主从复制的三个核心价值读写分离提升吞吐、热备保底提升容灾能力、隔离访问保护主节点。理解了这三点你就能明白为什么几乎所有的生产级Redis架构里都会有主从复制它不只是面试题更是Redis高可用体系的地基。1.2 主从复制的拓扑形态与核心概念主从复制在Redis里的模型非常直白一台主节点master负责处理写命令一台或多台从节点replica从主节点拉取数据变更保持数据一致。从节点默认只读除非你在配置里显式打开replica-read-only no否则业务不能往从节点写数据——这是为了规避主从数据分叉的麻烦。常见的拓扑有三种一主一从最基础的结构用于读写分离或热备。一主多从多台从节点分摊读流量还能把其中一台从节点用于后台任务比如生成报表、做数据全量导出、跑慢查询分析。树形主从链从节点下面再挂从节点比如master - replica - sub-replica。这种结构适合从节点数量特别多的场景避免所有从节点都直接连主节点导致主节点发送全量RDB时网络带宽被占满。这里需要提前区分一个重要概念主从复制是异步复制。主节点执行完写命令后立刻返回客户端不会等从节点确认写入成功。这意味着主从之间必然存在一个极短的时间窗口从节点的数据是「滞后」的。后面我会专门讲这个窗口如何量化、如何控制。1.3 主从复制不是高可用别搞混这是个特别容易混淆的点。很多人以为配置了主从复制就万事大吉主节点宕机后从节点会自动顶上来其实完全不是。主从复制本身不提供故障转移failover能力。主节点挂了从节点依然是从节点它不会自己变成主节点业务写入照样失败。真正做高可用需要额外的组件比如Redis Sentinal哨兵或者Redis Cluster集群。但无论哨兵还是集群它们的数据同步底座都是主从复制。哨兵做的事情是「监控主从状态、选主、通知客户端新主地址」而主从复制负责在哨兵完成切换后让新主继续从旧主那里继承数据如果有条件的话或者让其余从节点向新主同步。所以你在面试里可能被问到「主从复制和哨兵有什么区别」一个合格的回答应该是主从复制解决的是数据副本和读写分离问题哨兵解决的是高可用自动切换问题后者依赖前者但两者不在同一个层次。2. 主从复制原理拆解从SYNC到PSYNC2.1 主从复制的基础流程握手、同步、命令传播一次完整的主从复制过程拆开来看可以分为三个阶段。第一阶段是连接建立与握手。从节点启动后会根据配置项replicaof master_ip master_port向主节点发起TCP连接。连接建立后从节点会发送PING确认主节点存活然后进行身份认证——如果主节点设置了requirepass从节点必须在配置里写好masterauth否则认证失败复制连握手都过不去。认证通过后从节点会向主节点发送自己监听的端口号这个端口号用于主节点向从节点发送命令也能让主节点通过INFO replication看到从节点列表。第二阶段是数据同步。这一阶段取决于从节点的状态和主节点的积压情况可能是全量同步也可能是部分同步。细节我放在后面两节展开。第三阶段是命令传播。数据同步完成后主节点会把后续每一条写命令通过这个TCP连接持续发送给从节点从节点执行同样的命令来保持数据一致。这个阶段主节点还会定期向所有从节点发送PING心跳用来检测从节点是否还活着心跳间隔由repl-ping-replica-period控制默认是10秒。如果你在线上用netstat观察主从节点之间的TCP连接会发现一条长时间保持的ESTABLISHED连接这条连接上既跑着复制数据也跑着心跳和元数据通信。复制中断时这条连接会断开从节点会按照repl-timeout的语义进入重连流程。2.2 全量复制的内部细节与原理解读全量复制发生在两种典型情况从节点第一次连接主节点或者从节点断线太久导致部分同步条件不满足。全量复制的过程可以归纳为「一次RDB快照 一段增量命令」。第一次连接时从节点向主节点发送PSYNC ? -1表示「我没有任何历史数据请求全量同步」。主节点收到后会做两件事在后台执行BGSAVE生成当前数据的RDB快照。在生成快照期间所有新的写命令会进入一个叫**复制积压缓冲区repl_backlog**的内存区域。RDB生成完成后主节点把RDB文件发给从节点。从节点收到后先把旧数据清空然后加载RDB数据。这里有一个非常关键的细节从节点加载RDB期间主节点并没有停止写入这些新写入的命令全部堆积在积压缓冲区里。RDB加载完成后从节点会向主节点发送REPLCONF ACK offset主节点随后把积压缓冲区中从快照开始时刻之后产生的命令继续发给从节点。这样从节点的数据状态就追平了主节点。这里有一个容易被忽略的坑如果主节点开启了持久化RDB能用磁盘文件保留如果主节点关闭了持久化全量同步时主节点仍然会在内存中生成快照数据并发送给从节点但如果主节点在这个窗口内宕机由于没有磁盘上的RDB文件它自己都恢复不了更别提从节点。所以生产环境的Redis主节点最好不要关闭持久化除非你能接受极端情况下的全量数据丢失。在全量复制过程中如果数据集很大比如几个GB甚至几十GBRDB传输会占用大量带宽和CPU。Redis为此提供了无盘复制选项repl-diskless-sync主节点不走磁盘直接把内存快照数据流式地发送给从节点。但是要注意无盘复制模式下主节点会等所有满足条件的从节点都准备好后统一发送如果只有一个从节点设置repl-diskless-sync-delay可以让它立刻开始传输这个参数默认是5秒意味着最多等5秒才开始。如果你的Redis版本里没有这个参数说明太老建议升级。2.3 增量复制积压缓冲区、偏移量与PSYNC的配合Redis 2.8版本之前从节点断线重连后只能重新做全量复制代价很大。2.8版本引入了PSYNC协议支持部分重同步增量复制这是主从复制原理中最常被面试官深挖的部分。增量复制的核心是三个概念主从运行IDreplid、复制偏移量offset、复制积压缓冲区repl_backlog。主节点会维护一个随机生成的40位十六进制字符串作为运行ID从节点全量同步成功后会记住主节点的运行ID。当从节点断线重连时它会把「上次同步到的偏移量」连同主节点运行ID一起通过PSYNC replid offset发给主节点。主节点接到这个请求后做判断如果replid和自己当前的运行ID一致说明从节点之前就是从自己这里同步的。如果offset还落在自己的积压缓冲区范围内即offset之后的数据还在缓冲区内主节点就回复CONTINUE然后从缓冲区中把缺失的那段命令发给从节点完成增量同步。如果offset已经不在缓冲区内缓冲区的数据被新命令覆盖挤掉了或者replid对不上主节点重启产生新运行ID主节点只能回复FULLRESYNC从节点被迫重新全量同步。所以为什么要给积压缓冲区配一个足够大的值因为缓冲区本质上是一个固定大小的环形结构新命令不断写入旧命令不断被覆盖。如果缓冲区太小从节点断线稍微久一点断线期间的命令就把缓冲区撑满了重连后偏移量早被覆盖只能全量同步。全量同步在大数据集下代价极高所以线上一般建议把repl-backlog-size从默认的1MB调大比如设置成64mb或128mb具体取决于你的写入量和可容忍的断线时长。关于复制偏移量还需要说明一点从节点在复制完每条命令后会向主节点发送REPLCONF ACK offset告知自己当前的复制位置。主节点会根据所有从节点上报的偏移量判断每个从节点的数据延迟情况这些信息都体现在INFO replication中。主节点也会用WAIT命令等待指定数量的从节点确认写入不过WAIT只能保证从节点收到了命令不能保证命令真的执行成功这一点容易让人误解需要注意。2.4 版本演进SYNC、PSYNC与PSYNC2的差别Redis复制协议的演进是一个很好的理解线索。Redis 2.8以前只有SYNC从节点断线重连后无条件全量复制。数据量大的时候主节点压力巨大用户体验很差。Redis 2.8引入PSYNC支持部分重同步。从节点断线重连后如果符合条件可以在线补齐缺失命令避免了全量复制。Redis 4.0引入PSYNC2解决了主从切换后的复制问题。PSYNC2的核心改进是即使主节点发生了切换比如哨兵把从节点升级为主节点新的主节点会保留旧主的运行ID和复制偏移量历史其他从节点可以尝试向新主发起增量同步而不是一上来就全量复制。关于PSYNC2有一个很有意思的细节它通过「复制历史记录」让从节点在新主上执行部分重同步成为可能。新主节点会把前一个主节点的replid作为自己的second_replid保存当旧主的从节点来请求PSYNC 旧replid offset时新主节点如果发现自己保存了对应的历史且偏移量仍在积压缓冲区内就可以执行增量同步。这个设计非常巧妙也是故障切换后避免大量全量复制风暴的关键。理解了这条演进链路你就知道为什么Redis能在高可用架构下做到比较平滑的切换为什么INFO replication里会出现replid和replid2两个字段——replid2就是PSYNC2为旧主保留的历史身份。3. 从零搭建一主两从配置与实操3.1 安装Redis并准备配置文件实操之前先把Redis装好。以Linux服务器为例最简单的做法是编译安装wget https://download.redis.io/releases/redis-7.0.15.tar.gz tar xzf redis-7.0.15.tar.gz cd redis-7.0.15 make -j$(nproc) make install编译完成后redis-server和redis-cli会安装到/usr/local/bin/。Windows环境的话Redis官方不提供原生安装包一般用Memurai或者微软维护的移植版本配置逻辑是一样的。为了搭主从我习惯给每个节点准备独立配置目录。比如mkdir -p /etc/redis/{master,replica1,replica2} cp redis.conf /etc/redis/master/redis.conf cp redis.conf /etc/redis/replica1/redis.conf cp redis.conf /etc/redis/replica2/redis.conf生产环境不建议多个实例共用一份配置各节点独立配置目录便于隔离日志、数据文件和权限。3.2 直接修改配置实现主从replicaof与masterauth假设主节点IP是192.168.1.10端口6379。在从节点replica1的redis.conf里找到这样几行改掉注释replicaof 192.168.1.10 6379 # 如果主节点设置了requirepass这里必须配置同样的密码否则握手失败 masterauth your_redis_password replica-read-only yesreplicaof就是告诉这个Redis实例「你是谁的从节点」。masterauth是从节点向主节点认证用的密码很多新手只给主节点配了requirepass忘了在从节点配masterauth结果日志里反复报MASTER - REPLICA sync error: -NOAUTH Authentication required排查半天才发现是这个低级问题。主节点这边也可以配置一个「最小从节点数量」保护这是防止主节点在失去所有从节点后仍然接受写入导致脑裂的手段之一min-replicas-to-write 1 min-replicas-max-lag 10这两行含义是主节点至少要有1个从节点在线且从节点延迟不超过10秒才接受写命令。如果所有从节点都失联主节点会直接拒绝写入宁可牺牲可用性也要保护数据一致性。配置完成后分别启动实例redis-server /etc/redis/master/redis.conf redis-server /etc/redis/replica1/redis.conf redis-server /etc/redis/replica2/redis.conf启动后去任一从节点执行redis-cli -p 6380 info replication输出里能看到role:slave7.0版本显示role:replica以及master_link_status:up说明复制链路已经建立。3.3 用Docker快速搭建主从环境不少同学的本地开发机是macOS或者Windows用Docker搭主从最省事。先创建一个自定义网络保证容器之间能通过服务名互相访问docker network create redis-net启动主节点docker run -d --name redis-master \ --network redis-net \ -p 6379:6379 \ redis:7.0 redis-server \ --requirepass 123456 \ --appendonly yes这里用命令行参数方式直接指定密码和AOF持久化。接着启动两个从节点docker run -d --name redis-replica1 \ --network redis-net \ -p 6380:6379 \ redis:7.0 redis-server \ --replicaof redis-master 6379 \ --masterauth 123456 docker run -d --name redis-replica2 \ --network redis-net \ -p 6381:6379 \ redis:7.0 redis-server \ --replicaof redis-master 6379 \ --masterauth 123456注意Docker容器之间互访时replicaof里的主机名必须是容器名redis-master不是localhost。如果你在宿主机上用redis-cli -p 6380连进去看到的master_host会是redis-master这个容器名这是正常的。如果你希望用docker-compose把整个环境固化下来可以这样写services: master: image: redis:7.0 container_name: redis-master command: [redis-server, --requirepass, 123456, --appendonly, yes] ports: - 6379:6379 replica1: image: redis:7.0 container_name: redis-replica1 command: [redis-server, --replicaof, master, 6379, --masterauth, 123456] ports: - 6380:6379 depends_on: - master replica2: image: redis:7.0 container_name: redis-replica2 command: [redis-server, --replicaof, master, 6379, --masterauth, 123456] ports: - 6381:6379 depends_on: - master在docker-compose.yml所在目录执行docker compose up -d三节点环境就起来了。这套方案非常适合本地验证主从复制行为我日常排查问题经常这么干。3.4 验证主从复制的运行状态主从搭建好验证是必须的。在从节点上执行redis-cli -p 6380 info replication重点关注这几行输出我逐个解释role:replica当前节点是从节点。master_host/master_port主节点地址。master_link_status:up复制链路是否正常up表示正常。master_last_io_seconds_ago距离上次和主节点通信过去了多少秒如果这个值持续增大说明链路有问题。master_sync_in_progress:0是否正在全量同步。slave_repl_offset从节点当前复制偏移量跟主节点的偏移量对比就能算出延迟量。再看主节点的输出redis-cli -p 6379 info replication主节点上会列出所有从节点信息connected_slaves:2 slave0:ip172.18.0.3,port6379,stateonline,offset123456,lag1 slave1:ip172.18.0.4,port6379,stateonline,offset123456,lag1stateonline表示从节点在线offset123456是从节点当前已经复制的偏移量lag1表示主节点最近一次收到该从节点心跳的延迟秒数。验证读写数据时在主节点执行redis-cli -p 6379 set user:1001 hello然后分别到两个从节点执行get user:1001能拿到同样的值就说明复制生效了。再试一下往从节点写入redis-cli -p 6380 set user:1001 write-to-replica默认配置下会报错READONLY You cant write against a read only replica这是正常现象说明从节点的只读保护是开启的。4. 常见问题与排查技巧实录4.1 主从延迟与复制风暴的排查主从延迟是所有Redis主从架构里最先遇到的问题。你可以通过两个偏移量的差来量化延迟redis-cli -p 6379 info replication | grep master_repl_offset redis-cli -p 6380 info replication | grep slave_repl_offset两个值的差值就是当前积压的未复制命令字节数。如果延迟持续上涨排查思路按照下面这个顺序来检查主节点是否在执行大Key操作。比如DEL一个包含几百万元素的集合或者SETBIT一个非常大的字符串这类命令在从节点执行时同样耗时会造成复制命令堆积。排查方法是看主节点slowlog和CPU使用率。检查网络带宽。尤其是在做全量同步阶段RDB传输会占满带宽。也可以通过主节点INFO stats里的total_net_input_bytes和total_net_output_bytes观察网卡吞吐。检查从节点是否在做高负载操作。如果从节点同时被很多业务查询打满它的CPU没余力处理复制命令延迟自然会拉高。复制风暴是另一种场景很多从节点在同一个时间点全部断线重连或者大量从节点同时从主节点做全量同步导致主节点瞬间生成多份RDBCPU和网络同时打满。如果从节点数量很多建议用树形主从链结构让一部分从节点从「从节点」同步而不是全部挂在主节点上。另外一次性把repl-backlog-size调大也能减少断线重连后发生全量复制的概率。4.2 积压缓冲区不足导致频繁全量复制这是我在生产上踩过最深的一个坑。现象是一切看起来正常但从节点日志每隔一段时间就出现Full resync from master: ...然后主节点CPU飙高业务读到旧数据。查到最后发现repl_backlog_size默认只有1MB写入量稍微大一点从节点断线几秒后偏移量就掉出缓冲区了于是每次重连都触发全量同步。解决办法很简单调大积压缓冲区。计算公式大概是repl_backlog_size 每秒写入字节数 × 期望断线容忍秒数比如每秒大约写入2MB希望从节点断线30秒后还能增量同步那就至少设置64mb。线上我一般会设128mb因为内存便宜换来的是避免全量RDB传输的CPU和带宽开销完全划算。config set repl-backlog-size 128mb注意config set之后还需要config rewrite才能持久化到配置文件否则重启后失效。有一点需要提醒积压缓冲区的内存是从主节点内存中额外分配的如果实例本身内存已经接近maxmemory调大缓冲区可能导致内存压力变大甚至触发淘汰策略。设置时要把这部分内存算进总内存预算里。4.3 脑裂、数据不一致与min-replicas保护脑裂问题主要发生在哨兵高可用架构下。场景是这样的主节点和哨兵之间网络抖动哨兵判定主节点故障选举了一个从节点升级为新主。此时旧主节点并没有真死它还在接收业务写入。等旧主恢复后哨兵会把它降级为从节点让它向新主同步数据但因为旧主的写入从未同步给新主这些数据就被当作「多余数据」直接丢弃了。这个窗口期丢失的数据量理论上等于旧主节点孤立运行期间的全部写入。Redis官方给出的保护方案就是前面提到的两个参数min-replicas-to-write 1 min-replicas-max-lag 10意思是主节点失去所有在线从节点超过10秒后拒绝写入。这样即使发生脑裂旧主节点孤立运行超过10秒就会停止接受新写入丢失的数据窗口被限制在10秒以内。这里的关键是给min-replicas-to-write设置一个合理的值。如果你有3个从节点设成1表示至少1个从节点在线才接受写如果设成2保护更强但可能因为一个从节点临时断线就拒绝写入影响可用性。数据安全性和可用性之间怎么做取舍是每个Redis使用者必须面对的问题。我的建议是优先保证主节点写入可用min-replicas-to-write 1基本够了再配合每秒全量备份兜底。4.4 排查工具箱与速查表我把实际排查主从复制问题用到的命令和排查思路整理成一张速查表方便你对照处理。现象排查命令/配置常见原因master_link_status:down看从节点日志网络不通、密码不对、主节点宕机持续全量复制repl_backlog_size大小缓冲区太小断线重连后偏移量被覆盖主从数据延迟大对比master_repl_offset和slave_repl_offset大Key、慢命令、从节点负载高主节点不接受写入检查min-replicas-to-write从节点全部离线保护机制生效从节点无法认证检查masterauth与主节点requirepass不一致全量同步时主节点CPU飙高INFO stats、BGSAVE状态RDB生成和传输开销大考虑无盘复制主从切换后大量全量同步检查INFO replication里的replid2PSYNC2未生效或复制历史丢失还有一个我从一线总结的经验排查主从问题时先看从节点的日志文件日志里会非常明确地告诉你复制失败的原因比如-NOAUTH Authentication required、Cant get RDB file、MASTER aborted replication with an error。很多问题根本不需要猜日志已经把答案写在那里了。实操中还有一个值得养成的习惯在从节点上执行redis-cli --latency配合-h master_ip来监控主节点到本机的网络延迟如果延迟在几毫秒内波动说明网络没问题如果出现几十毫秒甚至上百毫秒就要重点检查跨机房的网络链路和带宽占用。最后分享一点个人体会。我前些年排查过一个诡异的复制故障从节点报MASTER timeout但master_link_status还是up业务方却反馈数据读不到。后来才发现是repl-timeout默认为60秒而主节点因为GC停顿超过了这个阈值导致从节点判定超时并重连。这个坑的教训是Reids的复制超时判断不是TCP超时而是IO空闲超时只要主节点在repl-timeout时间内没有向从节点发送任何数据包从节点就会认为复制超时。所以如果你发现从节点频繁重连、但网络和主节点负载都正常可以适当把repl-timeout调大比如调到120秒同时在主节点排查大Key扫描和内存碎片整理这类可能造成长时间停顿的操作。主从复制看起来简单真正跑在生产环境里要关注的细节远比想象中多。把这套原理吃透再去碰哨兵和集群你会觉得一切都顺理成章。
网站建设高端定制企业官网