Redis 学到现在,我最大的感受是:单机再好,也扛不住意外。前面几篇我整理过数据结构、内存模型、持久化手段,这一篇终于到了高可用层面最关键的一块——主从复制和哨兵。简单说,主从复制负责把数据从一台 Redis 复制到另外几台,哨兵负责在主库挂了之后自动把从库抬上位。这套组合是 Redis 生产环境高可用架构的基石,也是很多人从"会用 Redis"跨到"会维护 Redis"的分水岭。下面这些内容,是我从动手搭建、配置调优和故障演练里一条条记下来的,希望能帮正在学这块的人少走弯路。
1. 单机 Redis 再快也有扛不住的事
1.1 一台 Redis 的三重风险
Redis 性能确实好,单实例读写在很多场景下都能顶住,但它本质上还是一个进程,跑在一台机器上。只要机器出了问题,Redis 再快也白搭。我总结下来,单机 Redis 主要面临三类风险,每一类都不罕见。
- 机器层面的故障:断电、内存接触不良、磁盘损坏、系统崩溃导致重启。这些不是"会不会发生"的问题,而是"哪一天发生"的问题。
- 人为层面的误操作:
FLUSHALL、FLUSHDB、DEL一个大 key、执行了配置错误导致进程退出。很多线上事故不是攻击者干的,就是自己手滑。 - 网络层面的分化:机器本身没挂,但网络分区把主库隔离了。客户端连不到主库,业务照样受影响,而且这类问题排查起来最花时间。
所以,只要业务对 Redis 的可用性有一点要求,单机部署就属于"裸奔"状态。
1.2 主从复制解决什么,又解决不了什么
主从复制的核心价值可以拆成三点:
- 数据热备份:主库的数据实时追加式同步到从库,主库机器彻底报废时,从库上仍然有一份完整数据,不用从 RDB/AOF 冷恢复。
- 读能力扩展:主库负责写,从库分担读,Redis 的读写分离模式让整体吞吐量大幅提升。
- 高可用的底座:只有先把数据复制到多节点,后面才能谈自动故障转移,也就是哨兵干的事。
但有一点必须提前强调:Redis 主从复制是异步复制。主库执行完写命令后,不是等从库确认再返回客户端,而是直接返回成功,复制过程在后台异步完成。这意味着它天生无法保证数据零丢失。如果主库在某个写命令还没同步给从库时就宕机了,这条最新的写入就丢了。理解了这一点,再看后面的脑裂和数据丢失问题就容易多了。
2. 主从复制的底层链路:从一次 psync 聊起
2.1 第一次建立关系时,主从之间发生了什么
很多人配置完主从就完事了,不太关心数据到底怎么过去的。实际上,第一次建立复制关系时,走的是一条完整的"全量同步"链路。我把每一步拆出来看。
假设现在我有一台主库和一台从库,我在从库上执行:
redis-cli -p 6380 REPLICAOF 127.0.0.1 6379从库会做三件事:记录主库地址、建立 TCP 连接、发送PSYNC命令。PSYNC是复制的握手协议,语法长这样:
PSYNC <replid> <offset>如果是初次复制,从库不知道主库的 replid,也不知道自己该从哪个偏移量开始,所以会带上?和-1:
PSYNC ? -1主库收到后,如果判断无法做部分同步,就会回复FULLRESYNC,同时把自己的replid和当前复制偏移量发给从库,然后进入全量同步流程:
- 主库执行
BGSAVE,生成 RDB 快照。 - 与此同时,主库把新收到的写命令写入复制积压缓冲区和命令传播缓冲区,防止快照生成期间的数据丢失。
- 快照生成完成后,通过 socket 发给从库。
- 从库清空旧数据,加载 RDB 文件。
需要注意的是,Redis 为了避免磁盘 I/O 成为瓶颈,可以改用磁盘无盘化复制(repl-diskless-sync yes),RDB 在内存中直接生成并通过网络流式传输,副本不落盘。这个参数在从库较多时能明显降低主库磁盘压力,但要求主从之间网络带宽充足。
- 从库加载完 RDB 后,主库还会把复制缓冲区里的增量命令继续发过来,确保数据追平。
- 之后双方进入命令传播阶段,主库每次执行写命令,都会把命令分发给从库执行。
整个全量同步的安全点在于:BGSAVE生成快照的过程不阻塞主库服务,但仍会消耗 CPU 和内存,如果数据量特别大,建议在业务低峰期做首次复制。
2.2 断线重连背后的增量同步机制
全量同步只发生在首次建立关系、或者从库落后实在太多的情况下。日常运行中,从库和主库之间网络抖动导致短暂断线,重新连接时再走一次全量同步就太浪费了。Redis 解决这个问题的机制叫部分重同步。
主库内存里维护了一块固定大小的环形缓冲区,叫复制积压缓冲区(repl_backlog)。主库每执行一条写命令,都会把命令写入这个缓冲区,同时自己的master_repl_offset也往后推进。从库这边同步到哪条命令,也会记录自己的slave_repl_offset。
断线重连时,从库发送:
PSYNC <主库replid> <从库记录的offset>主库接过请求后会检查:
- 主库 replid 是否匹配,不匹配就只能全量同步。
- 从库 offset 之后的数据,是否还在本地的复制积压缓冲区中。
如果 offset 之后的数据仍在 backlog 覆盖范围内,主库就回复CONTINUE,把从缺口开始的那一段命令重新发给从库。如果 offset 已经超出了缓冲区范围,说明落后的太多,主库才回复FULLRESYNC,走全量同步。
这个机制的一个直接结论就是:backlog 缓冲区的大小要按实际断线容忍时间配置。比如生产环境平均每秒写 2000 次,每次命令平均 200 字节,每秒就是 400KB 的数据量。默认的repl-backlog-size是 1MB,算下来只能容忍约 2.5 秒的断线。如果你的网络抖动可能持续 1 分钟,这个值至少得配 24MB。缓冲区配大一点,代价是内存占用增加;配小了,断线稍久就会退化成全量同步,消耗更大。
2.3 主从复制中容易被忽略的三个细节
第一,主从复制并不是主库主动推数据。从库启动后先打招呼,主库才把数据交给它,之后每次命令传播也是从库在 TCP 连接上等待。理解这个模型,故障排查时就明白为什么网络出问题后从库会不断重连,而不是主库单方面断开。
第二,从库默认只读,配置项是replica-read-only yes。如果强行在从库上执行写命令,Redis 会直接报错。这样设计既保证一致性,也防止开发者把从库当成写入口导致数据错乱。
第三,过期键的处理是主库说了算。主库遇到 key 过期时,会显式向从库发送一条 DEL 命令。从库不会自己判断 key 是否过期并删除,只等主库指令。这样可以避免主从各自判断时出现数据不一致。不过从库仍然会在读取时判断逻辑过期,返回空值给客户端,内存里的清理还是靠主库指令。
3. 建立主从关系:配置实操与关键参数
3.1 三种建立方式怎么选
建立主从关系有三种方式,方式本身并不复杂,但背后的适用场景很有讲究。
| 方式 | 操作 | 特点 | 适用场景 |
|---|---|---|---|
| 命令行 | REPLICAOF host port | 即时生效,重启后失效 | 临时切换、演练、手动故障转移 |
| 配置文件 | 在 redis.conf 写replicaof host port | 持久生效,需重启实例 | 正式部署、长期架构 |
| 启动参数 | redis-server --replicaof host port | 持久生效,依赖启动脚本 | 容器化、进程管理工具拉起 |
我在实际部署时偏爱"配置文件为主、命令行临时调整"的组合。平时保持主从关系写在配置里,需要做故障演练或主从切换时,才用命令行动态操作。
有一点要特别注意:执行REPLICAOF no one可以让从库解除复制关系、变为主库,但这条命令同样不会写入配置文件。如果机器重启,它会重新按配置去连原主库。生产环境做切换时,一定要把配置文件一起改掉,否则机器一重启,架构又变回原样,事故就埋下了。
3.2 五个值得提前调好的配置项
repl-backlog-size:复制积压缓冲区大小。刚才已经算过,需要按每秒写入量和断线容忍时间估算,一般 16MB 起步。repl-timeout:复制超时时间,默认 60 秒。主从之间如果超过这个时间没有心跳,从库会判定主库不可用,尝试重新连接。网络环境较差时可以适当调大,比如 90 秒。repl-diskless-sync:是否启用无盘化全量同步。多从库首次全量复制时建议开启,减少磁盘 I/O,但需要确保主从之间的带宽充足。replica-read-only:从库只读开关,生产环境必须保持 yes。repl-diskless-sync-delay:无盘化同步开始前的等待时间,默认 5 秒。这个设计很有意思,主库会等一等,看这段时间内有没有新的从库来请求复制,如果有,可以用一次 RDB 覆盖多个从库,避免每个从库重复生成快照。
3.3 如何在命令行验证复制状态
配置完成后,在从库执行:
redis-cli -p 6380 INFO replication关注这几项:
role:slave master_host:127.0.0.1 master_port:6379 master_link_status:up master_last_io_seconds_ago:0 master_repl_offset:10086 slave_repl_offset:10086 master_replid:e5a3f8...如果master_link_status是up,说明连接正常。master_repl_offset和slave_repl_offset数值一致时,主从数据追平。实际环境中这两个偏移量会存在短暂差异,只要差值不大、且不断收敛,就是正常的。
我还建议把主库侧的connected_slaves也盯上。主库用INFO replication可以看到当前挂着几个从库,以及各自的复制偏移量。这个视角才是运维真正需要的。
4. 主库宕机后,哨兵到底在忙什么
4.1 没有哨兵时,手动切换到底有多累
先想想看,如果只有主从复制、没有哨兵,主库宕机后运维要干的活:
- 确认某个从库的数据恢复程度(看偏移量是否追平)。
- 在从库上执行
REPLICAOF no one,把它升为主库。 - 修改所有业务客户端的 Redis 地址,指向新的主库。
- 把原主库降级为从库,重新接入新主库。
这一套流程听下来不难,但每一步都要人工判断,而且业务中断时间取决于人的反应速度和操作熟练度。大半夜被叫起来处理故障,能保持清醒做完四步已经很不错了。哨兵存在的意义,就是把这件事自动化。
4.2 主观下线和客观下线:哨兵判断主库挂了的两层逻辑
哨兵判断主库是否宕机,不是一口咬定,而是分两步。
第一步叫主观下线(SDOWN)。每个哨兵进程每隔一秒会向所有节点发送 PING 命令,如果主库在down-after-milliseconds配置时间内都没有有效回复,当前这个哨兵就会认为主库主观下线。
注意,这只是单个哨兵的看法。由于网络波动、GC 停顿等原因,单个哨兵可能误判,所以需要引入第二步。
第二步叫客观下线(ODOWN)。当一个哨兵判断主库主观下线后,它会向其他哨兵发起询问:"我认为主库挂了,你们怎么看?" 其他哨兵根据自己的探测结果给出回应。当赞成票数达到配置的quorum阈值时,这个哨兵就确认主库客观下线,可以进入故障转移流程。
这里有一个关键点:quorum不是哨兵集群的所有节点数,只是确认下线所需的最低票数。假如我有 3 个哨兵,quorum配成 2,那么至少两个哨兵认定主库下线,流程才会启动。哨兵节点数量建议奇数个,这是为了后续选主投票时能形成多数派,避免平票。
4.3 故障转移的完整决策链
确认客观下线后,接下来发生的事情顺序很有意思:
- 哨兵节点之间选出一个领导者,由它负责执行故障转移。这个选举机制会让每个哨兵自荐或者推荐别人,拿到半数以上票的哨兵成为领导者。
- 领导者从所有从库中选出一个新的主库。选择规则按优先级依次是:
slave-priority配置值越小越优先;优先级相同时,复制偏移量最大的从库优先(数据最完整);再相同,按 runid 字典序小的优先。 - 新主库执行
REPLICAOF no one,从库身份变为主库。 - 其他从库执行
REPLICAOF <新主库IP> <新主库端口>,重新指向新主库。 - 原主库恢复可用后,哨兵会把它降级为从库,也指向新主库。
整个过程从故障到切换完成,正常情况几秒到十几秒。业务客户端需要能感知哨兵切换,通常用哨兵的发布订阅消息或访问哨兵来发现新的主库地址。
5. 哨兵最小化部署与故障演练全过程
5.1 一套最小但完整的高可用架构
我建议学习阶段就搭一主两从三哨兵,不要偷懒只搭一个哨兵。单个哨兵自己都可能成为单点,故障转移时也无法形成多数派判断。部署规划可以这样:
| 节点 | 端口 | 作用 |
|---|---|---|
| 主库 | 6379 | 读写入口 |
| 从库 A | 6380 | 冷备 + 读 |
| 从库 B | 6381 | 冷备 + 读 |
| 哨兵 1 | 26379 | 监控和故障转移 |
| 哨兵 2 | 26380 | 监控和故障转移 |
| 哨兵 3 | 26381 | 监控和故障转移 |
实验环境下可以跑在同一台机器上,只要端口区分就行。生产环境至少需要三台机器,哨兵分布在不同的物理节点上,否则机房整体断电会让哨兵集体失效。
5.2 哨兵配置文件逐项说明
哨兵的配置文件是sentinel.conf,核心项如下:
# 监控的主库名、地址、端口、判定客观下线所需票数 sentinel monitor mymaster 127.0.0.1 6379 2 # 主库无响应多久后判定主观下线,默认30000毫秒 sentinel down-after-milliseconds mymaster 5000 # 故障转移超时时间,默认180000毫秒 sentinel failover-timeout mymaster 180000 # 故障转移时最多可以同时向几个从库发起同步 sentinel parallel-syncs mymaster 1逐项解读:
sentinel monitor里的最后一个数字就是quorum,我这里是 2,意味着至少两个哨兵认定主库下线才切换。down-after-milliseconds设成 5000,即 5 秒无响应就报主观下线。生产环境要根据网络质量调,太短容易误判,太长切换太慢。parallel-syncs建议设 1,意思是新主库上位后,同一时间只允许一个从库全量同步。如果同时让多个从库一起同步,新主库可能因为瞬间负载过高被拖垮。
5.3 手动杀掉主库,观察哨兵的完整动作
搭建完成后,我强烈建议做一次故障演练,不然哨兵配置到底有没有生效,心里没底。我当时的演练过程如下。
先把所有节点进程拉起,确认哨兵已经发现了主库和从库:
redis-cli -p 26379 sentinel masters redis-cli -p 26379 sentinel slaves mymaster然后模拟主库故障,直接杀掉主库进程:
redis-cli -p 6379 SHUTDOWN NOSAVE接着盯着哨兵日志看变化。典型的日志顺序是:
+sdown master mymaster 127.0.0.1 6379 +odown master mymaster 127.0.0.1 6379 #quorum 2/2 +try-failover master mymaster 127.0.0.1 6379 +failover-state-select-slave master mymaster 127.0.0.1 6379 +selected-slave slave 127.0.0.1:6380 +promoted-slave slave 127.0.0.1:6380 +switch-master mymaster 127.0.0.1 6379 127.0.0.1 6380看到+switch-master就说明切换已经完成。此时再用哨兵查询主库地址,返回的应该是 6380 端口。同时,原先的从库 B 也会自动指向 6380,整个集群恢复可用。
演练完之后,我把原 6379 重启,观察哨兵日志,会发现它被自动接入为从库,指向 6380。这一步很关键,因为它验证了原主库恢复后不会"抢占"主库位置,而是老老实实做从库。
6. 复制延迟、脑裂与哨兵系统的边界
6.1 复制延迟从哪里来,怎么盯
主从复制是异步的,所以延迟必然存在。延迟的来源主要有三个:
- 网络往返:主从跨机房部署时,RTT 本身就有几十毫秒。
- 大 key 与慢命令:主库在生成 RDB 或执行大集合操作时,可能阻塞复制链路。
- 从库自身负载:从库如果承担了大量读请求,CPU 跑满时处理复制命令也会变慢。
监控方式就是在主库执行:
INFO replication观察master_repl_offset和每个从库的slave_repl_offset,二者差值就是复制积压量。更省事的方式是利用 Redis 的监控指标工具(如命令行--stat或各类可视化监控)定期采集这个差值并告警。实际运营中,延迟长期超过 1 秒就需要排查了,尤其是读写分离场景下,延迟会导致读到过期数据。
6.2 脑裂场景与最后一道防线 min-replicas
脑裂是主从复制模式下最经典的"数据丢得神不知鬼不觉"的场景。
场景是这样的:主库和哨兵之间的网络发生了分区,哨兵联系不上主库,于是把从库 A 提升为新主库。但原主库并没有宕机,它还活着,而且客户端仍然能通过原地址连接到它,继续写入数据。等网络恢复,哨兵发现原主库还活着,强制它变成从库,去同步新主库的数据。此时原主库在分区期间写入的新数据就全被覆盖了,因为这些数据根本没同步给任何节点。
这个问题的防线在 Redis 配置里,叫min-replicas-to-write和min-replicas-max-lag。含义是:主库至少要有 N 个从库连接,且数据复制延迟不能超过 M 秒,否则拒绝处理写请求。
min-replicas-to-write 1 min-replicas-max-lag 10意思是:如果连一个从库的复制延迟都超过 10 秒,那主库就不再接受写入。
在脑裂场景里,原主库被分区隔离,从库全部连不上它,复制延迟迅速超过阈值,写入被拒绝,从而避免了分区期间的数据产生。当然,代价是这段时间业务写入直接失败,属于"牺牲可用性保一致性"的选择。实际配置时怎么取舍,取决于业务特性,但min-replicas-to-write至少设成 1 是常规建议。
6.3 哨兵模式解决不了的问题
哨兵不等于数据零丢失。主库宕机时还没有同步给从库的那部分数据,切换后依然会丢。如果业务对数据完整性要求极高,需要在客户端层面做补偿,或者接受这个误差,否则就得考虑其他方案。
哨兵模式下主库仍是单点写入口。从库虽然可以扩展读,但写入请求最终还是要打到主库。如果写并发很大、单机内存放不下,哨兵解决不了这种容量问题,需要走 Redis Cluster 的分片方案。
哨兵集群不是配置完就一劳永逸。哨兵之间的网络同样可能分区,quorum和节点数量需要结合实际情况认真设计。我在实际搭建时发现,哨兵日志的输出信息量大但分散,最好在前期就把日志接入统一的监控平台,否则真出事时只能靠人肉翻日志。
最后再分享一个我自己的小习惯:每次搭完主从和哨兵,我都会在从库上执行几遍INFO replication和SENTINEL masters,确认所有节点视角一致后再交付。高可用这套东西,风险都不在配置本身,而在配置和现实状态对不上。多演练、多观察、多记录,真正出事时才能心里有数。