Redis主从复制原理与运维实战:从搭建、排错到面试高频考点
在Redis的日常使用里,很多人是从单机版起步的,等到业务量上来后,第一反应是加缓存、加内存,或者直接上集群。但有一个非常基础却又容易被忽视的环节,就是主从复制。Redis主从复制解决的不仅是读写压力问题,更是数据安全和故障恢复的底线。这篇文章我会从自己实际搭建和运维的角度,把主从复制的原理、配置、排错和面试高频考点一次讲透,适合正在做后端开发、缓存运维,或者准备系统设计相关面试的同学参考。
1. 为什么我从单机Redis切换到主从架构
Redis在项目里扮演的角色通常是缓存、Session存储、计数器或者分布式锁的载体。单机部署最简单,启动一个进程,配好密码,业务代码一接,稳稳当当。但稳当的前提是流量可控、数据可丢。等我把压测数据拉出来看的时候,问题就很明显了:单机QPS撑到一定程度就开始抖动,更重要的是,一旦这台机器宕机或者内存数据损坏,所有热点数据全部失效,上游数据库会被瞬间打爆,整个服务链路的可用性直接归零。
主从复制带来的第一个收益就是读写分离。主节点负责写,从节点负责读,虽然Redis本身处理读写都很快,但把读流量分散到从节点之后,主节点的CPU和内存压力会明显下降。尤其是我当时负责的一个业务,读多写少的比例大概在10比1,这种场景下主从架构的收益非常明显。
第二个收益是故障恢复。虽然没有自动故障转移,但有了从节点的完整数据副本,主节点挂掉之后,我可以手动把从节点提升为主节点,或者配合哨兵实现自动切换。哪怕不做自动切换,至少数据还有一份热备,不至于连底裤都丢掉。
第三个收益是数据备份。以前我备份Redis数据用的是定期bgsave,再把RDB文件拷到远端。有了从节点之后,可以把备份任务直接挂到从节点上,主节点完全不受备份任务的影响。对于读取一致性要求不高的场景,这是相当省钱又省心的操作。
顺便说一句,如果你是在Windows上学习,本地用redis desktop manager连一下,或者直接命令行里跑,操作逻辑和Linux完全一样,只是在生产环境我强烈建议用Linux部署Redis。Windows版本更多是学习验证用的,性能和文件句柄处理还是Linux更靠谱。
2. 主从复制到底在复制什么
很多刚接触Redis主从的人以为,主从复制就是把数据“原样拷一份”过去。这个理解方向是对的,但实现机制远不止“拷贝”这么简单。只有吃透复制的过程,后面遇到数据不一致、复制断点、主从切换之类的问题,才不至于一头雾水。
2.1 认识复制积压缓冲区和复制偏移量
Redis从节点的复制跑起来之后,每个节点都会维护两个关键的数字:一个是主节点的复制偏移量(master_repl_offset),一个是从节点的复制偏移量(slave_repl_offset)。这两个数字记录的是各自处理过的复制流的字节位置。正常情况下,从节点的偏移量会无限逼近主节点的偏移量,二者之差越小,说明数据越新鲜。
主节点除了准备RDB快照之外,还会维护一个固定大小的环形缓冲区,叫复制积压缓冲区(repl_backlog)。它本质上是内存里的一段环形存储,记录最近一段时间主节点收到的写命令。这个缓冲区大小由配置项repl-backlog-size控制,默认是1MB。这个值非常关键,直接决定了网络断开后能否做部分重同步。
我举个实际例子你就明白了。假设主节点每秒产生5MB的写入量,所有从节点断开3秒,那这段时间产生了15MB的写命令。如果repl_backlog只有1MB,这些命令早就被新命令覆盖冲掉了,从节点重连回来之后,发现自己需要的偏移量已经不在缓冲区里,只能被迫走全量重同步,重新生成RDB并传输,代价非常大。所以repl-backlog-size的建议值是结合写入量和断网容忍时间算出来的,公式很简单:每秒写入量乘以你希望容忍的最大断连秒数,再留出一些余量。
2.2 全量复制和部分复制的执行链路
主从第一次同步时,从节点没有任何数据,必须做全量复制。流程大致是:
- 从节点向主节点发送psync命令,带上自己的runid和偏移量。第一次连上时,runid未知,从节点会发psync ? -1。
- 主节点发现无法匹配,便触发bgsave生成RDB快照,同时把后续新写入的命令持续写入复制积压缓冲区。
- RDB生成完毕后,主节点把RDB文件发给从节点,从节点清空旧数据,加载RDB。
- RDB加载完成后,主节点把缓冲区内积累的增量写命令一并发给从节点。
- 从节点执行完这些增量命令后,就和主节点保持实时同步了。
部分复制则聪明得多。当从节点因为网络抖动断开重连后,会带上自己之前的偏移量发psync。主节点检查发现这个偏移量仍在复制积压缓冲区范围内,就不再做全量RDB传输,而只是把从节点缺失的那一小段命令发给它。这个机制让网络短暂抖动带来的开销变得极小,也正是repl_backlog存在的意义。
关于runid,也值得多说一句。每个Redis实例启动时都会生成一个40位的十六进制runid,标识节点的身份。主从同步开始后,从节点会保存主节点的runid。如果主节点重启了,runid会变,从节点再发psync带上旧runid时,主节点发现不匹配,就只能被迫全量重同步。这个问题在日常运维中非常常见,后面我会专门讲怎么尽量避免。
2.3 命令传播阶段:从节点如何保证实时更新
全量复制完成之后,RDB只是一次性快照,后续主节点的每一次写命令,都需要同步到从节点。Redis采用的是基于命令传播的方式,也就是说,主节点每次执行完写命令,除了写入自己的数据文件,还会把这条命令以Redis协议格式推送到所有从节点。从节点收到后,在自己的数据集中执行一遍同样的命令,从而保证数据一致。
这里有个关键点,主从节点之间的数据一致性是最终一致性,从节点的数据天然会有毫秒级甚至秒级的延迟。原因很简单,命令传播走的是网络IO,肯定有耗时。如果你的业务对读取一致性要求极高,刚写入的数据必须马上能读到,那这种数据就不应该走从节点读取,否则就得分清楚场景。
另外,从节点默认是只读的,配置文件里叫replica-read-only,默认yes。这个设计是为了避免从节点被误写入,否则两边数据不一致了很难排查。如果你在做某些特殊架构,比如用了Redis的pub/sub做消息广播,或者确实需要从节点接受写入,那也要谨慎评估风险,别让数据产生分叉。
3. 搭建一套能扛压的主从环境
讲完原理,就该实操了。我会把几种常用部署方式都过一遍,包括Linux二进制方式、Docker Compose方式以及Windows本地学习方式。不管哪种方式,核心配置文件里的参数是通用的。
3.1 节点规划与关键配置项
我以一个经典的三节点主从架构为例:
| 角色 | 节点地址 | 端口 | 用途 |
|---|---|---|---|
| 主节点 | 192.168.1.10 | 6379 | 接收写入 |
| 从节点1 | 192.168.1.11 | 6379 | 读流量、备份 |
| 从节点2 | 192.168.1.12 | 6379 | 读流量、容灾 |
每个节点都需要在redis.conf中重点确认以下配置:
- bind:建议绑定内网IP或0.0.0.0,不要裸奔在公网。
- protected-mode:生产环境建议显式设置。
- requirepass / masterauth:主节点设置访问密码,从节点必须配置masterauth,否则复制时认证不通过。
- replicaof:从节点指定主节点的IP和端口,格式是replicaof 192.168.1.10 6379。
- repl-backlog-size:根据写入量评估,务必显式设置,别用默认1MB。
- repl-read-only:从节点保持只读。
- replica-priority:从节点优先级,哨兵选举时会参考,数字越小优先级越高。
这里特别提醒一下masterauth这个坑。很多人设置完主节点密码后,忘了在从节点配置masterauth,导致从节点日志里一直刷NOAUTH Authentication required。复制链路建立不起来,数据一直为空。而且这个参数一旦缺失,从节点不会主动报错,只是反复重试认证,需要你仔细看日志才能发现。
3.2 命令行方式与配置文件方式搭建
如果你已经有一个Redis实例,最快验证主从复制的方式是直接用命令。在从节点上执行:
执行完replicaof命令后,从节点会立刻向主节点发起同步请求。用info replication命令可以查看主从状态:
从节点上则应该看到role:slave,以及master_link_status:up。看到这个状态,说明同步链路已经建立。命令方式的好处是即时生效,不需要重启进程,适合临时扩容或者快速验证。缺点也很明显,Redis进程一旦重启,这个关系就丢了,所以生产环境还是得落到配置文件里。
配置文件方式就很简单了,在从节点的redis.conf里加一行:
然后启动从节点时加载这个配置文件。注意,改了配置文件之后,如果原有实例已经在运行,需要重启进程或者用config rewrite把运行时的配置持久化到文件。
3.3 Docker Compose方式部署主从
现在很多团队都是用Docker部署Redis,好处是环境一致、扩容方便。Docker Compose部署主从的配置我给你一套可直接用的模板:
需要注意,Docker网络里从节点通过服务名redis-master去解析主节点IP,这就是容器网络和宿主机网络的区别。如果你把从节点暴露到宿主机之外,比如让外部程序也连接从节点,记得把需要监听的端口映射出来。
另外,Docker方式下AOF和RDB持久化也要一起考虑。我在上面的模板里开了appendonly yes,就是为了防止容器重启后数据全部丢失。生产环境里,Redis容器本身状态管理还好,但数据持久化挂载到宿主机是刚需。
3.4 Windows本地学习环境如何验证
如果你是在Windows上学习Redis,没有Linux服务器,也不用担心。Windows版Redis虽然官方支持力度不如Linux,但学习主从复制足够了。
方法很简单:解压两份Redis,一份当主节点,一份当从节点。主节点默认6379端口,从节点改配置文件里的port为6380,再加一行replicaof 127.0.0.1 6379,然后分别启动。在从节点命令行里用redis-cli连6380,试试get主节点写入的key,能读到就说明同步成功。
Windows下有一个注意点:Windows版Redis的配置文件里默认很多参数是注释掉的,你改了port之后要确认没有重复定义。另外,用redis desktop manager这类可视化工具连的时候,如果连不上,先看Windows防火墙有没有放行对应端口。
4. 运维中真正会踩的坑:延迟、全量重同步与脑裂
主从架构搭起来只是开始,真正折腾人的是跑起来之后的那些问题。我把自己踩过的一些坑整理出来,并按排查链路来写,希望你遇到的时候能少走弯路。
4.1 主从延迟变大时的排查链路
从节点数据延迟变大,最直接的影响是读多写少场景下,业务会读到旧数据。延迟从毫秒级变成秒级甚至分钟级,这肯定不正常。我当时的排查思路是这样的:
第一步,先看从节点的lag指标。在从节点上执行info replication,关注master_repl_offset和slave_repl_offset的差值,以及输出里的lag字段。如果lag持续增大,说明从节点应用命令的速度跟不上主节点产生命令的速度。
第二步,看主节点是否频繁生成RDB。如果你发现主节点一直在bgsave,而网络带宽又不充裕,从节点在第一次全量同步后,又会因为偏移量落后而被要求全量同步,形成恶性循环。这时候需要确认RDB生成频率和网络吞吐。
第三步,看从节点机器本身的负载。如果从节点部署在同一台物理机上还跑着其他吃CPU的服务,Redis处理命令的能力会被严重挤占。我自己就遇到过,从节点机器上跑了一个定时任务,每到整点CPU飙到100%,Redis的复制延迟立刻冲高。
第四步,检查大Key问题。如果某个Hash或者Set类型的数据特别大,从节点执行对应的写命令时,内存分配和数据结构操作耗时就会很长,拖慢整条复制流的应用速度。这种情况最好的处理方式是把大Key拆分,或者调整业务存储结构。
4.2 主节点重启后触发大量全量重同步的破解方法
文章前面提到过,Redis启动时runid会重新生成。从节点发现runid变了,原有的psync偏移量匹配不上,就只能全量重同步。如果从节点只有一个还好,但如果有几十个从节点同时重新全量同步,主节点的CPU、磁盘、网络立刻被打满,甚至影响正常业务。
要缓解这个问题,有几个办法:
一是调整主节点的重启方式。在安全的前提下,如果只是配置改动需要重启,尽量使用config rewrite先保存配置,再用redis-cli shutdown nosave关闭,最后重新启动时带原有的持久化文件。但runid每次启动都会变,这个无法避免。
二是提高repl-backlog-size。虽然runid不匹配会导致从节点发起全量同步请求,但如果你在重启前把RDB和AOF都完整落盘了,从节点全量同步的数据量其实不会太大,瓶颈更多在文件传输和加载上。大一点的repl_backlog虽然不影响主节点runid变化后的全量同步,但在从节点短暂断线重连时能兜住更多增量数据,减少不必要的全量同步发生。
三是使用Redis哨兵或集群的故障转移机制,让从节点在主节点故障时自动提升,而不是被动等待原主节点重启后重新全量同步。从节点提升为主节点后,其他从节点会指向新的主节点重新同步,链路更清晰,也能避免老主节点回来之后带着一堆从节点一次性全量重启。
四是新版Redis引入了repl-diskless-sync,从节点可以通过Socket直接接收RDB数据流,不用先把RDB文件落到磁盘再加载。对于磁盘IO慢而网络带宽充足的场景,这种方式能有效缩短全量同步时间。
4.3 主从切换时的脑裂与数据丢失
脑裂这个词,我在做Redis架构之前,只在ZK和Kafka那边听过。但主从架构配合哨兵时,同样有脑裂风险。场景是这样的:主节点和哨兵之间的网络发生分区,但主节点和客户端之间还能正常通信,客户端继续往主节点写数据。此时哨兵因为联系不上主节点,会触发故障转移,把一个从节点提升为新主节点。等网络恢复后,旧主节点重新连上集群,发现自己已经变成了从节点,会把新主节点作为自己的主节点进行全量同步,而这个同步过程会先清空旧主节点上的本地数据。这就意味着,分区期间旧主节点上产生的新写入数据全部丢失。
这个问题在架构层面没有完美的解,只能尽量规避。首先,主节点上可以配置min-replicas-to-write和min-replicas-max-lag。打个比方,min-replicas-to-write 1表示主节点至少要有一个从节点在线,否则不再接受写入;min-replicas-max-lag 10表示从节点延迟超过10秒也算下线。这样当网络分区发生时,如果从节点已经和主节点失联超过阈值,主节点就会拒绝新的写入,宁愿牺牲可用性,也要保住数据不产生分叉。
我当时遇到这个问题时,权衡再三后把这两个参数都配上了。虽然极端情况下会让主节点在某些异常状态下短暂拒绝写入,但从数据安全的角度来说,这个代价是值得的。尤其是一个核心业务链路,任何数据分叉都可能导致对账和补偿逻辑变得极其复杂。
4.4 从节点磁盘满或文件系统异常的恢复顺序
还有一个容易被忽视的坑,就是从节点磁盘写满导致RDB落盘失败。从节点同步时会先接收主节点传来的RDB文件,如果本地磁盘空间不足,整个同步流程就会失败,而且从节点会不断重试。这个现象会让人误以为是网络问题或主节点问题,其实看一眼从节点的日志,里面会明确提示写文件失败或者磁盘配额超限。
处理办法很直接,清理出足够空间,然后在从节点执行replicaof no one,再重新执行replicaof 主节点IP 端口,强制触发一次干净的全量同步。或者直接重启从节点进程也行。我在实际运维里遇到过两次这种磁盘问题,一次是日志文件占满磁盘,一次是另一个业务把同一块磁盘写爆了。排查思路都是先df -h看磁盘,再dmesg或journalctl看文件系统报错,最后清理空间并重置复制关系。
5. 从面试角度重新理解主从复制
主从复制是Redis面试中必然被问到的知识点,而且面试官一般不会只问你“怎么配主从”,而是会深挖原理和场景。作为一个面过别人也被面过的人,我梳理几个高频考点。
5.1 psync的演进:从sync到psync再到psync2
Redis 2.8之前只有sync命令,做的是全量同步。网络断一次就全量一次,效率很低。Redis 2.8引入了psync,支持部分重同步,这才有了主从断线后快速追平的能力。Redis 4.0进一步优化了psync的容错机制,也就是大家常说的psync2,当主节点故障重启后,从节点可以基于旧的复制历史继续尝试部分重同步,而不是一重启就强制全量。理解这个演进过程,会让你在面试里显得更有底蕴。
psync的执行流程我上面已经说过了,但面试官还喜欢追问一个点:如果从节点发送的偏移量落在复制积压缓冲区之外,主节点会怎么做?答案是从节点会收到一个+FULLRESYNC的响应,然后重新走全量复制流程。这就是为什么缓冲区和偏移量是绑在一起理解的核心概念。
5.2 数据一致性与复制风暴
面试中还有一个常见的延伸问题:主从复制如何保证数据一致性,以及为什么会有复制风暴。数据一致性方面,你要明确指出Redis主从是最终一致性,而不是强一致。在一个严格的金融级场景里,如果写入主节点后立刻从从节点读回来,可能读到的还是旧值,这不能算系统故障,而是主从架构的设计特点。
复制风暴指的是大量从节点同时向主节点请求全量同步,导致主节点压力激增。触发的原因可能有主节点重启后runid变化、网络分区导致大量从节点同时重连,或者刚扩容了一批从节点。应对方案除了前面提到的加大repl_backlog,还可以使用树状复制结构,也就是让部分从节点作为下一层从节点的“主节点”,把同步压力分散下去。
5.3 主从复制和哨兵、集群的层级关系
面试官通常会顺着主从复制继续问哨兵和Cluster。你要记住这条脉络:主从复制解决的是数据副本和读写分离,哨兵解决的是自动故障转移,Cluster解决的是数据分片和水平扩展。三者是递进关系,不是互相替代的关系。
哨兵模式下,每一个哨兵都会监控主节点和从节点的健康状况,通过投票机制选出新的主节点。这个过程中,哨兵会根据从节点的优先级、复制偏移量、runid等信息做综合判断。这也是为什么我在配置里特意设置了replica-priority,高优先级的从节点在故障转移时会更快被选中。
Cluster模式和主从复制的关系也紧密。Cluster中每个分片的主节点都可以带若干从节点,这些从节点用的还是主从复制的机制,只不过由Cluster的gossip协议负责维护节点间的状态。理解了主从复制,再去看Cluster的故障转移,会觉得顺畅很多。
6. 一些实战习惯与收尾建议
说了这么多,最后分享几条我这几年用Redis主从复制攒下来的实战习惯,谈不上什么高深技巧,但确实能让人省心不少。
第一,所有配置变更尽量用config set先在线修改,观察稳定后再config rewrite持久化。不要总是改完配置文件就重启Redis,重启引入了runid变化和全量同步的风险,尤其在核心链路里,每一次重启都要慎之又慎。
第二,监控告警一定要做。主从延迟、从节点离线数、复制积压缓冲区使用量、主节点内存增长速率,这些指标都应该接入监控系统。告警阈值可以不用太严格,但至少要保证主从链路断了之后,你能在两三分钟内知道。
第三,主从复制不是数据备份的替代品。虽然从节点有完整副本,但如果你误执行了del key,所有节点会同步删除,谁都救不了你。真正能救命的是定期RDB备份加AOF日志,还要把备份文件冷备到另外一个机房或者对象存储上。
第四,当你发现自己需要频繁手动操作主从切换时,就是时候考虑引入哨兵或者直接上Cluster了。主从复制解决了一部分问题,但它不是一个完整的“高可用方案”。它能让你在半夜出故障时快速恢复,但没法做到让业务无感知。结合自己的业务体量,该上哨兵就上哨兵,别等到大促前再临时抱佛脚。
最后再补充一个很多人忽略的点:主从节点的硬件配置尽量保持一致,尤其是内存和CPU。从节点如果配置远低于主节点,大流量下很容易成为瓶颈,而且会被误判为故障节点。我见过一个项目,主节点32G内存,从节点只给8G,结果从节点频繁触发内存淘汰策略,数据不一致,排查了大半天才找到根因。这套架构跑得好不好,七分在原理理解,三分在运维细节,该花的成本,真的一分都不能省。