先聊个特别常见的场景:你手里的Redis服务平时跑得挺稳,直到某一天它突然挂了,然后整个应用跟着一起不可用,排查半天发现就是单点故障——一台Redis扛所有读写,挂了就全没了。这时候你就知道Redis主从节点这套东西有多重要了。
所谓主从节点,本质就是一台主节点(master)负责接收写请求,一个或多个从节点(slave/replica)负责同步主节点的数据,对外提供备份、读流量分摊和故障兜底能力。它解决的问题很直接:单点故障、读压力过大、误操作后数据恢复。无论你是刚接触Redis想做高可用,还是已经在生产环境里被故障坑过想补课,这篇文章都值得你花十分钟看完。
我会把原理、部署、验证、坑这几块一次讲透,并且给出可以直接照抄的命令和配置。
1. 主从节点到底解决了什么,先认清这三个核心价值
1.1 单点故障:一台机器扛所有压力的大坑
Redis默认是单机运行的,所有数据都存在一台机器的内存里。平时看着没什么问题,可一旦这台机器陷入僵死、内存被写爆或者所在宿主机出现异常,整个服务立马不可用。更麻烦的是,数据如果开启了持久化还好,最多丢一部分数据;如果没有合理配置持久化,那连家底都可能一次性清零。
这就是主从架构的第一个核心作用:给数据做实时副本。主节点照常处理业务请求,从节点在后台持续同步数据。一旦主节点出问题,你手里至少还有一份完整的数据副本,不至于两手空空。比如我在实际运维中遇到过Redis所在磁盘IO频繁打满的情况,主节点阻塞将近半分钟,如果没有从节点,那段时间所有缓存请求全部直接穿透到数据库,依赖缓存的接口全崩。
1.2 读写分离:把读压力从主节点上拆走
Redis本身就是单线程模型,所有命令在它内部是串行执行的,同一个时刻只能处理一条命令。虽然它处理单条命令的速度很快,但扛不住大量并发读。如果业务又是典型的读多写少,比如商品详情、用户信息、配置中心这类场景,大量读请求全部压在同一个实例上,CPU和网络带宽很容易成为瓶颈。
主从架构天然适合读写分离。写操作、事务性操作、需要强一致性的读全部走主节点,能接受短暂延迟的读操作走从节点。比如一个秒杀商品页,库存、状态等关键写操作只打到master,商品介绍、评论列表这些非关键读就可以分摊到多台slave上。这样一来,主节点的压力就小了很多,整体吞吐量能提升好几倍。
1.3 数据容灾:多一份副本,多一层保险
除了故障和生产压力,人总是会犯错的。比如凌晨三四点在线上环境误执行了一条FLUSHALL或者DEL了一个大key,发现的时候数据已经被清了。如果只有一台Redis且开启了持久化,还有机会通过RDB或AOF恢复;如果持久化策略写得不理想,或者刚巧在这段时间内覆盖了旧快照,那就真的回天乏术了。
但如果你配了主从,从节点上还保留着故障发生前的数据副本,你可以从从节点快速恢复数据,可以把从节点临时提升为主节点继续服务,也可以直接在从节点上执行BGSAVE拿到一份最新的RDB文件。说白了,主从架构就是给数据多买了一重保险,关键时刻救命用的。
2. 主从复制的核心原理,这几个词搞不明白肯定踩坑
2.1 数据是怎么从master流向slave的
Redis主从复制的数据流,从整体上看可以拆成三个阶段:握手建立连接、数据快照传输、增量命令传播。
从节点启动后会主动连接主节点,如果配置了密码还需要带上认证信息。握手成功后,从节点发送同步请求PSYNC <replid> <offset>,告诉主节点自己希望从哪个复制ID、哪个偏移量开始同步。如果是第一次同步,主节点会比较复制ID和偏移量,发现无法衔接,就会触发一次全量同步。
全量同步的核心动作是主节点执行BGSAVE生成RDB快照,然后把RDB文件发送给从节点,从节点清空旧数据后载入这份快照。为什么是全量?因为从节点没有任何历史上下文,主节点无法知道从节点缺了哪些数据,最稳妥的办法就是把当前完整数据打包传过去。
等RDB加载完成,主从之间的数据才算对齐到某一时刻。但注意,在BGSAVE和RDB传输期间,写命令还在不断进来。主节点不会丢下这些命令不管,而是把它们写入了复制积压缓冲区和自己的AOF缓冲,随后以命令流的方式持续转发给从节点。这一阶段也叫命令传播(command propagating),从节点只要保证跟上主节点的节奏,就能长期保持数据一致。
2.2 全量同步与增量同步的分界线
很多人在面试里被问过“PSYNC和SYNC有什么区别”,其实就是全量同步与增量同步的划分。早期版本只有SYNC,每次断线重连都要重新传一次全量RDB,数据量大时效率非常低。后来引入PSYNC,支持部分重同步,也就是只补传连接断开期间缺失的命令。
判断走全量还是增量,核心看两个信息:一个是主节点的replid(复制ID),另一个是复制偏移量offset。从节点发起PSYNC时,会带上自己记录的主节点replid和已接收的偏移量。主节点收到后先比对replid是否一致,不一致说明从节点之前连的不是自己,或者自己重启过导致复制ID变化,这时只能全量同步;如果replid一致,再看偏移量是否还在复制积压缓冲区(repl-backlog)里。
如果从节点的偏移量落后不多,主节点可以直接从积压缓冲区里取出缺失的命令发送过去,网络传输量就很小,从节点也能快速追平。但如果从节点落后太多,积压缓冲区已经覆盖不到那个偏移量了,主节点没办法补全缺失部分,只能重新来一次全量同步。
这里有一个容易忽略的点:repl-backlog-size默认是1MB,如果主节点写流量很大,几秒钟就能把缓冲区写满。一旦从节点断线超过这个窗口,再重连就得全量同步,面对几十GB的数据,网络和磁盘都吃得很难受。所以生产环境里,这个值要根据写QPS和平均命令大小去估算。
2.3 复制偏移量、run_id和积压缓冲区,到底怎么配合
要真正理解主从复制,必须把三个核心概念串起来。
第一个是replid,也叫复制ID,相当于主节点的身份标识。Redis每次启动时会随机生成一个新的41位十六进制字符串,从节点会记录自己当前跟随的主节点replid。如果主节点发生了重启,replid就变了,老从节点再连上来会发现replid对不上,被迫触发全量同步。这也是为什么Redis主节点重启这件事在生产环境里要尽量谨慎。
第二个是复制偏移量,主节点每发送一条命令,就会给这条命令打上一个字节级偏移量;从节点每接收并应用一条命令,也会记录自己当前的偏移量。两边一对比,就知道从节点落后了多少。通过INFO replication命令里的master_repl_offset和slave_repl_offset,你可以很直观地看到主从之间的差距。
第三个是复制积压缓冲区,这是主节点内存里的一个环形队列,用来保存最近传播过的写命令,专门给断线重连的从节点补数据用的。因为它是环形覆盖的,新的写命令会不断覆盖最旧的数据,所以它能覆盖多远的过去,完全取决于缓冲区大小和写命令的流量。你可以粗略估算:假设平均每条写命令100字节,每秒写入2000条,那每秒产生大约200KB数据,默认1MB的缓冲区大约只能覆盖5秒的断线窗口。这个数字是不是有点惊到你了。
3. 亲手搭一套主从,两种部署方式完整实操
3.1 Docker快速部署Redis主从环境
现在是容器化时代,本地验证主从架构最方便的方式就是用Docker,不需要去系统里编译和排查依赖,几分钟就能起来一套。
假设你已经装好了Docker,先创建一个自定义网络,让两个容器可以通过容器名互相访问:
docker network create redis-net然后启动主节点容器:
docker run -d --name redis-master \ --network redis-net \ -p 6379:6379 \ redis:7.0再启动从节点容器,关键是追加--replicaof参数,告诉它谁才是master:
docker run -d --name redis-slave \ --network redis-net \ -p 6380:6379 \ redis:7.0 \ redis-server --replicaof redis-master 6379启动之后,进到从节点容器里确认状态:
docker exec -it redis-slave redis-cli info replication如果看到role:slave、master_link_status:up,说明主从已经建立。这里提醒一下,Redis 5.0以后官方把slaveof改成了replicaof,虽然老的slaveof命令仍然兼容,但新的配置里建议直接用replicaof。
如果主节点设置了密码,从节点启动时还要带上对应的认证信息,否则连接会被拒绝:
redis-server --replicaof redis-master 6379 --masterauth yourpassword3.2 Linux下源码包部署Redis主从
生产环境里,很多机器上的Redis并不是容器部署的,而是源码包直接编译安装的。这里说一下最常见的源码安装方式。
先去Redis官网或者通过wget下载源码包,比如7.0版本:
wget https://download.redis.io/releases/redis-7.0.10.tar.gz tar -zxvf redis-7.0.10.tar.gz cd redis-7.0.10 make && make install编译完成后,把配置文件复制出来,分别准备master和slave两份配置。master节点只管监听端口和数据持久化相关配置,通常不需要额外修改。从节点的核心配置是这一行:
replicaof 192.168.1.10 6379放在redis-slave.conf里,然后启动:
redis-server /etc/redis-slave.conf如果从节点和主节点不在同一网段,或者有防火墙策略,记得保证从节点能通过6379端口访问到主节点。还有许多云环境下,主节点绑定了本机内网IP,从节点在另一台机器上访问内网IP是正常的,不要拿外网IP去试。
3.3 验证从节点连接状态的正确姿势
搭建完成后,怎么判断主从是否真的工作正常?很多人只是看主从节点进程起来了就觉得万事大吉,但进程活着不代表复制是通的。
用redis-cli连接任意节点,执行:
redis-cli -p 6379 info replication输出里重点关注这几个字段:
role:当前节点的角色,master或者slaveconnected_slaves:主节点视角下,已连接的从节点数量slave0:从节点列表,包含IP、端口、状态、偏移量master_host、master_port:从节点视角下,master的地址和端口master_link_status:从节点与master的连接状态,up为正常,down为断开master_last_io_seconds_ago:多久之前和master有过IO交互,如果持续增长,说明同步可能卡住了
我在验证时还会做一个动作:在主节点上写一个测试key,然后立刻到从节点上去读取。
redis-cli -p 6379 set sync_test ok redis-cli -p 6380 get sync_test能读到说明数据流是通的。不过要注意,主从复制是异步的,写入后不是百分百立刻能在从节点读到,正常场景下延迟是毫秒级,但严格意义上你写完后立即去读从节点,仍然可能读不到,这在设计主从架构时要提前知道。
4. 主从架构的高级玩法与应用场景
4.1 一主多从:搭建标准的读写分离架构
生产环境最经典的主从部署,就是一主多从,比如一台master加两台slave。master负责写入,slave负责处理读请求,这样既分摊了读压力,也保证了数据冗余。
搭建方式很简单,再启动一个slave容器或实例,给它配置同样的replicaof指向master即可。需要注意的是,多个从节点同时触发全量同步时,会对主节点产生很大的瞬时压力。主节点要同时执行BGSAVE并分发多份RDB数据,内存、磁盘IO和网络带宽都可能被瞬间打满。所以不要一次性把十个从节点同时挂上去,分批加,隔几秒再加一个,能有效降低复制风暴风险。
读写分离场景下还要考虑客户端的路由策略。你的应用里要有明确的读写分离逻辑,比如Spring RedisTemplate可以把读请求指向从节点、写请求指向主节点。但别忽略一个重要问题:Redis主从复制是异步的,从节点上的数据永远可能比主节点旧几毫秒甚至更久。如果业务对数据一致性要求苛刻,比如读后写、写后立刻读同一份数据,这类请求必须强制走主节点,否则可能出现刚写入却读不到的情况。
4.2 级联复制:解决从节点数量过多的延迟问题
当从节点数量很多时,每个从节点都直接从主节点同步数据,主节点既要承担业务写入,又要向所有从节点分发命令,网络带宽和CPU开销都会上升。这时候可以采用级联复制。
所谓级联,就是某个从节点同时作为更下一层从节点的master。架构上变成:
- 主节点master
- 从节点slave-1,同时作为下一层的master
- 从节点slave-1-1
- 从节点slave-1-2
- 从节点slave-2
- 从节点slave-1,同时作为下一层的master
配置方式很简单,在slave-1上不仅配置replicaof master_host 6379,同时允许其他从节点连接它,下一层从节点配置replicaof slave-1_host slave-1_port即可。
级联模式的好处是能显著减轻主节点的复制压力,数据从一个节点向多个节点扇出,适合从节点非常多、或者跨机房的场景。但劣势也很明显:链路变长后,底层从节点的数据延迟会进一步加大。如果最底层的从节点承载了实时性要求较高的读请求,要小心评估延迟是否在接受范围内。
4.3 加一层哨兵:从主从迈向真正的高可用
讲到这里必须提一嘴,单纯的主从架构并不等于高可用。原因很多人会忽略:如果没有额外的故障转移机制,当主节点宕机时,从节点只会一直尝试重连主节点,并不会自动把自己提升为新的master。这时候整个Redis服务实际上是处于只读状态,所有写请求都会失败。
解决这个问题的标准方案是Redis Sentinel(哨兵)。哨兵进程会持续监控主从节点的健康状态,当主节点被判定为客观下线后,哨兵会从从节点中选举一个提升为新的master,然后通知其他从节点去复制新master。应用端通过哨兵获取当前master地址,就可以在故障转移后自动切换。
主从加哨兵,是Redis高可用领域用得最广的一套组合。它比手动切换可靠得多,也比Cluster集群更简单,适合大多数中小规模的业务。不过哨兵本身的部署也有讲究,最少要三个哨兵实例且独立部署,避免哨兵自身成为单点。这部分坑也比较多,后面可以单独写一篇聊哨兵的选主过程,这里先把主从的地基打牢。
5. 主从环境下常见问题与排查经验实录
5.1 主从数据不一致,先查这四个地方
主从之间数据偶尔不一致,是运维Redis时最常碰到的困扰之一。这里整理一下我排查过的典型原因和对应思路。
第一,网络延迟或带宽瓶颈。从节点接收命令的速度赶不上主节点产生命令的速度,导致偏移量差距越来越大。用INFO replication对比两边的offset,如果差值持续增长,优先排查网络延迟、主从机器负载以及是否有大key在传播。
第二,repl-backlog-size设置得太小。断线重连时如果发现offset已经不在积压缓冲区范围内,会触发全量同步。全量同步期间,主节点依然在处理写命令,但从节点只能在加载完RDB后继续接收新增命令,这期间数据窗口较大。解决方案是调大repl-backlog-size,比如从默认的1MB调整到256MB甚至1GB,视写入量而定。
第三,从节点有本地写入。Redis从5.0之后,从节点默认是replica-read-only yes,只允许读不允许写。但如果你手工改成了no,从节点上被写入的数据这些值不会同步回主节点,但会被后续主节点的命令覆盖。这种不一致通常很隐蔽,查配置就能发现。
第四,过期key的处理。Redis主从对过期key的处理机制比较特殊,主节点在key过期时会生成一个DEL命令传播给从节点,从节点依据这个DEL进行删除。但如果从节点上有应用直接读取一个在本地已过期的key,Redis的惰性删除策略会在从节点读操作时发现过期并删除它。在某些极端时间窗口内,从节点可能短暂暴露已过期的数据。对于需要严格一致的场景,这个特性必须心里有数。
5.2 从节点写入报错READONLY,不是配置坏了
很多人头一次连接从节点想写入数据时,会看到这样一行报错:
(error) READONLY You can't write against a read only replica.
这个报错不是故障,而是Redis的保护机制在起作用。从7.0开始,从节点默认是只读的,不允许应用写入。这样设计的目的,就是为了防止从节点上的本地数据和主节点冲突,避免服务无意的写入造成数据混乱。
如果你确实需要临时在从节点上执行写操作,可以在从节点的配置里改replica-read-only no,然后重启或者用CONFIG SET replica-read-only no动态调整。但这是极其不推荐的做法。一旦从节点写入没有同步回主节点,后续主节点对同一个key的更新会直接覆盖掉你的写入,数据一致性无从谈起。我在生产环境里从没见过哪个合理场景需要让从节点开放的,大多数“从节点写不进去”的问题,本质上都是客户端路由配置错误,把应该走master的写请求发到了slave。
5.3 主节点宕机后,从节点不会自动上位
这是主从架构使用中误解最深的一个点。很多新手会以为,master挂了以后slave会自动接管成为新的master。实际上不会。没有哨兵的情况下,master宕机后,slave会一直处于等待重连的状态,它只是保留数据副本,但不会主动变成主节点,也不会接受写请求。
这时候你只能手动处理,比如用REPLICAOF NO ONE命令把一个从节点提升为新的主节点,其他从节点再切换到这个新主节点。整个过程需要人工介入,而且应用的写入端也要同步切换连接地址,业务中断时间取决于你的响应速度。
如果希望故障转移不再依赖人工,就必须引入哨兵Sentinel。哨兵会监控master的状态,确认客观下线后发起故障转移,从中选出一个新任master。这套机制能自动完成整个切换过程,应用端通过哨兵感知新master地址。所以主从本身是数据备份和数据分发方案,主从加哨兵才是高可用方案,逻辑上这两个概念不要混为一谈。
5.4 处理复制风暴、大key卡顿和重启元凶
最后再分享几个我在实际运维中踩过且算是比较高频的坑。
第一个是大key复制导致的同步卡顿。如果Redis里存了一个几百MB的list或hash,主节点传播这个key时,会一次性把整个value序列化后发送,从节点也要一次性加载并写入内存。这个过程可能造成网络传输阻塞、从节点命令处理阻塞,甚至触发全量同步超时。主从架构下,必须在上层就控制好单个key的尺寸,比如大集合拆分成多个小key,或者用其他存储承载大对象。
第二个是主节点重启引发的连锁反应。前面说了,主节点重启会生成新的replid,所有从节点都会因此触发全量同步。如果你有几十个从节点和级联节点,一次重启可能引发一轮复制风暴,把整个集群流量打满。我在生产环境里换master配置时,都会提前做一次主从切换,避免直接重启master。
第三个是缓冲区配置。对写并发较高的场景,repl-backlog-size默认值远远不够。我通常会把master节点的repl-backlog-size设到512MB以上,配合min-replicas-to-write和min-replicas-max-lag这类参数,在主节点异常时不至于让不可靠的副本继续接收写流量,这是生产环境里很实用的一招。
在实际操作中我特别建议养成一个习惯:在任何主从节点上执行破坏性命令之前,先用INFO replication确认自己的角色和当前拓扑,再动手。很多时候你以为你连的是master,实际上配了一个从节点,结果一条FLUSHALL直接把线上缓存清了,这种事故光想想都冒冷汗。
Redis主从节点这套东西,认真梳理下来其实并不复杂,核心就是数据同步、角色分工、故障兜底这几件事。真刀真枪部署一遍、亲手把master打到故障再完成一次手动切换,比看十篇文章都管用。等主从这一层跑得足够稳了,再往上加哨兵、演进到Cluster,心里就有底了。