
人家说“Redis 挂了就挂了重启就行”真到了线上环境主节点宕机、业务写不进去、缓存全失效的几分钟里你才会明白什么是“抓瞎”。我在这块踩过的坑不算少最早做的主从复制从节点只能顶着读流量主节点一挂还得人工改配置、手动切换运气好赶在半夜没人用运气不好就是线上事故。后来把哨兵集群搭起来才算真正睡了个踏实觉。这篇文章就用我实际搭过的一套架构把 Redis 哨兵集群从设计思路、配置细节到故障切换实测完整走一遍给准备做高可用改造、或者正在面试被问到“哨兵原理”的朋友一个可以直接抄作业的参考。1. 先弄清楚为什么要上哨兵集群1.1 主从复制到底缺了什么Redis 主从复制大家都不陌生一个 Master 负责写多个 Slave 负责读Master 挂了理论上把某个 Slave 提升为主业务就能继续。但问题是“提升”这个动作谁来做传统方案里是人工。我最早就是纯主从线上架构是 1 主 2 从平时看起来稳得很。结果某天晚上主节点所在机器磁盘满了Redis 直接拒绝写入我一觉醒来看到监控报警才赶紧连上去看发现主库虽然没宕机但已经持续写失败半个多小时了。那半小时里所有写入都失败业务日志一堆报错这就是没有自动故障转移的代价。人工处理还有一个常见大坑你以为把某个 Slave 用 SLAVEOF NO ONE 提升成主库就完了实际上旧的主库恢复后还会以原来身份继续接收写入这时新旧主库谁都不服谁数据直接裂开根本没法收敛。这种场景下哨兵集群的价值就体现出来了它能自动完成“发现故障、选举新主、切换配置、通知客户端”的完整闭环。1.2 哨兵集群解决的三个核心问题从架构演进的视角看哨兵集群补齐了三个能力第一监控。每个 Sentinel 节点每秒钟会向它认识的 Master、Slave、其他 Sentinel 节点发送 PING如果对方在规定时间内没有响应就认为它“主观下线”。这个“主观”很有意思因为单节点判断可能受网络抖动影响所以有了第二点。第二自动故障转移。当多个 Sentinel默认是 quorum 数量都认为 Master 下线就会进入客观下线状态然后开始从健康的 Slaves 中选一个提升为新的 Master。这个选举过程有完整协议能保证只有一台机器最终被选为新主。第三通知。哨兵会把新的 Master 地址动态推送给客户端客户端不用重启、不用改配置只要通过哨兵获取地址就行。这也是 Redis 官方推荐的生产接入方式。哨兵集群里通常至少部署 3 个 Sentinel 节点彼此之间也能互相感知。它解决的痛点是主从复制虽然解决了读扩展但并没有解决高可用而单哨兵本身又是个单点一旦它自己也挂了整个高可用机制就失灵了。1.3 和 Cluster 模式怎么选这一点我经常被问到既然有 Cluster为什么不直接上集群说实话如果是几 GB 级别的缓存数据业务的读写分离和自动切换诉求为主哨兵模式 主从复制是完全够用的。Cluster 更适合数据量大、需要水平扩展多个分片的场景它把数据按 slot 分布到多个主节点上复杂度高不少键的批量操作也受限制。另外Cluster 模式下某些命令行为会变化比如 MGET 跨 slot 会报错对已有的业务代码可能要做较大调整。所以我的选型经验是核心诉求是高可用和故障自动切换数据量控制在单机物理内存可承受范围哨兵模式是最稳妥、最节约成本、也最容易维护的方案。如果数据量大到单机扛不住再考虑 Cluster这俩不是替代关系而是不同阶段的不同解法。2. 搭建前的规划拓扑、端口、机器2.1 一套可以直接用的拓扑架构我实际搭建过一套生产环境用的机器配置不算高4 核 8G三台云主机架构如下节点角色节点地址端口用途Redis Master10.0.0.106379主节点读写主入口Redis Slave-110.0.0.116379从节点只读备份Redis Slave-210.0.0.126379从节点只读备份Sentinel-110.0.0.1026379和主节点同机Sentinel-210.0.0.1126379和从节点同机Sentinel-310.0.0.1226379和从节点同机注意这里我刻意把三个 Sentinel 分散在三台机器上而不是像某些教程那样放在同一台。道理很简单哨兵集群的决议机制靠“多数派”3 个哨兵分在三台机器只要集群内任意 2 台网络互通就能形成多数派决策。如果哨兵全堆在一台机器上那台机器宕机哨兵机制直接失效这跟没搭建高可用没区别。另外生产环境下哨兵端口 26379 一定不要暴露在公网只允许内部网络访问这个后面配置防火墙的时候要盯紧。2.2 操作系统与部署环境准备我这边统一用的是 CentOS 7.9内核版本在 3.10 以上满足 Redis 6.x 的运行需求。三台机器的 hostname 我会设置成有意义的名称比如 redis-master、redis-slave-1、redis-slave-2方便日志排查。虽然 IP 在配置文件里也能写但实际运维时你一看到日志里出现 redis-master:6379比看到一串数字 address 要直观得多。装 Redis 前先做三件事把系统时间用 ntpdate 同步一下避免日志时间线混乱哨兵故障转移的判定时间可都靠它。关闭透明大页也就是把 /sys/kernel/mm/transparent_hugepage/enabled 改成 never并写入 rc.local这能减少 Redis 在 fork 子进程时出现延迟卡顿的概率。调整 vm.overcommit_memory 为 1防止 Redis 后台持久化时因为内存分配策略导致 fork 失败同时把 /proc/sys/net/core/somaxconn 调到 1024 以上配合 timeout 配置避免高并发下连接被内核拒绝。这些系统参数看起来琐碎但每一个背后都有真实的线上事故案例。我见过一台机器明明内存充足因为 overcommit 默认策略限制BGSAVE 直接失败导致 RDB 备份中断一整夜的场景。Redis 官方文档把这几项写在 recommended configuration 里不是没有原因的。2.3 版本选择为什么我建议 6.xRedis 版本现在有很多分支我还是建议直接用 6.0 以上稳定版比如 6.2 系列。一个关键原因是 6.0 之后引入了多线程 IO虽然读写命令执行依然是单线程但网络 IO 读写并发能力提升明显应对高并发场景更从容。另外官方对老版本的维护周期有限新版本修复了很多安全漏洞直接用新版本可以少操心不少安全补丁问题。下载安装时我喜欢用编译安装虽然 yum 仓库里也有 redis但版本往往较老配置路径也不按我的习惯来。编译安装的套路固定下载源码包解压后 make make install几分钟就能搞定。不过要注意编译前记得先装 gcc不然会卡在编译报错上一脸懵。3. 主从复制搭建的完整实操3.1 基础配置项先从一份干净的 redis.conf 开始安装完成后我从 Redis 源码包里的 redis.conf 复制一份作为基础配置。Redis 自带的示例配置注释非常详细很适合照着改。以下是我认为主从模式下必须显式确认的关键项配置项我的推荐值说明bind0.0.0.0 或内网 IP生产环境绝不要裸奔到公网protected-modeyes开启保护模式有密码时不至于被外部扫描port6379默认端口可改daemonizeyes后台运行便于 systemd 管理pidfile/var/run/redis_6379.pid进程管理依赖logfile/var/log/redis/redis_6379.log独立日志排查故障必备dir/data/redis/6379持久化文件目录requirepass你的强密码主从间认证和客户端访问都靠它appendonlyyesAOF 持久化避免故障恢复丢失过多数据appendfsynceverysec每秒刷盘性能与安全折中这里尤其要强调 requirepass 的一致性。主节点设置了密码从节点配置里也有 masterauth如果两边密码不一致从节点会不断尝试握手失败日志里全是MASTER - REPLICA sync started然后立刻报-NOAUTH Authentication required。这个问题非常典型新手踩坑率极高。需要注意的是不同 Redis 版本的配置项名称略有变化比如旧版叫 slaveof新版统一叫 replicaof但语义是一样的。我这次用 6.2写的就是 replicaof。3.2 设置主从关系的两种方式配置主从有两种思路一种是改配置文件适合长期固定关系另一种是临时执行命令适合测试验证方式一从节点的 redis.conf 里加入replicaof 10.0.0.10 6379 masterauth your-strong-password然后重启 Redis 服务从节点会自动开始同步。方式二在从节点上用命令动态设置redis-cli -a your-strong-password replicaof 10.0.0.10 6379这个命令立即生效不需要重启。如果后面想取消从节点身份在对应节点执行redis-cli -a your-strong-password replicaof no one我个人建议生产环境用配置方式因为一旦机器重启命令方式的配置不会保留而配置文件方式能保证重启后主从关系依然存在不用人工再敲一遍命令。3.3 验证主从复制是否正常配置完成后第一步在主节点上写入一条测试数据redis-cli -a your-strong-password SET health-check ok然后在从节点上读取这条 key能读到说明复制链路正常。接着在从节点执行redis-cli -a your-strong-password INFO replication重点看两个字段role:slave master_link_status:upmaster_link_status 为 up代表从节点和主节点的连接处于健康状态。如果显示 down需要去从节点的日志文件里看具体报错。还有一个容易被忽略的指标是 master_last_io_seconds_ago如果这个值持续飙升说明主从之间的网络存在延迟虽然连接没断开但同步已经明显滞后了。主从复制起来后平时的读流量可以分流到从节点但注意要处理数据一致性问题。因为主从复制是异步的从节点数据可能落后于主节点对一致性要求高的读取场景还是得走主节点。这个我在后面故障转移章节还会重点分析。4. 哨兵集群配置与启动细节4.1 三份哨兵配置文件的重点项哨兵的配置文件名通常是 sentinel.conf。下面这份配置是我从头到尾手敲过很多遍的版本我挑其中几个核心参数做讲解。以 Sentinel-110.0.0.10为例port 26379 daemonize yes pidfile /var/run/redis-sentinel-26379.pid logfile /var/log/redis/sentinel-26379.log sentinel monitor mymaster 10.0.0.10 6379 2 sentinel auth-pass mymaster your-strong-password sentinel down-after-milliseconds mymaster 5000 sentinel parallel-syncs mymaster 1 sentinel failover-timeout mymaster 15000 protected-mode no严格来说保护模式下哨兵之间也需要互相访问如果你把 protected-mode 设成 yes且没有显式配置 bind 和可信 IP 范围可能出现哨兵之间无法通信的情况。我这里是内网环境直接设为 no但一定配合防火墙精确放行来源 IP。每个参数背后的意思我从源码逻辑的角度简单拆解一下。sentinel monitor这一行是哨兵的核心注册信息接四个参数被监控的 Master 名字、IP、端口、以及判定客观下线所需的 quorum 数量。名字我习惯用业务命名比如 mymaster、order-master后面所有配置项都要引用这个名字。quorum 设为 2 意味着只有至少 2 个 Sentinel 节点都认为 Master 主观下线才会触发客观下线和故障转移流程。所以三台哨兵里坏掉一台不会影响判定坏掉两台就无法形成多数派哨兵集群自己都选不出领导者更谈不上切换了。sentinel down-after-milliseconds是主观下线判定时间我调成 5000 毫秒。这个值不是越小越好太小了一次轻微的 GC 停顿或者网络抖动就可能触发误切太大了故障发生后业务受影响时间太长。我线上实际用 5 秒加上故障转移本身的时间整体 RTO 控制在 15 秒以内对大部分业务来说可以接受。parallel-syncs是故障转移完成后允许同时对新主发起全量同步的从节点数量。设成 1是为了避免多个从节点同时向新主发起 BGSAVE 和全量同步导致新主机器瞬间压力过大。sentinel failover-timeout则覆盖整个故障转移阶段的最大容忍时间包括选举、配置分发、从节点重指向等步骤。一旦超时本次故障转移被认为失败哨兵会重新调度重试。15 秒是相对保守但安全的配置。4.2 启动顺序与常见启动坑启动顺序有一个基本原则先启动 Redis 主从再启动 Sentinel。两个从节点启动完成后按顺序在三台机器上各自启动 Sentinelredis-sentinel /etc/redis/sentinel.conf启动后第一时间查看日志和进程状态。哨兵启动时会在日志里打印类似这样的关键信息RUN command user: redis monitor master mymaster 10.0.0.10 6379 quorum 2看到这行说明哨兵已经成功监控到 Master。然后可以用命令确认三台哨兵之间是否互相感知到对方redis-cli -p 26379 sentinel master mymaster redis-cli -p 26379 sentinel sentinels mymaster redis-cli -p 26379 sentinel replicas mymaster我来解释下这三条命令分别代表什么。第一条查看 Master 本身的当前状态第二条查看这个监控组里的其他哨兵节点是否已被发现如果只能看到自己说明哨兵互相通信有问题第三条查看当前识别到的从节点列表数量应该和实际从节点数量一致。如果 sentinels 列表里只有自己大概率是 Sentinel 之间端口不通或者 bind 配置限制了连接来源。这种时候先去查防火墙再确认配置文件里 bind 0.0.0.0。4.3 客户端到底怎么接入哨兵哨兵集群搭好之后业务侧连 Redis 的写法也要跟着变不能继续直接连主节点的 IP 了。高可用的思路是客户端先连哨兵询问当前 Master 是谁然后走 Master 做读写。如果发生故障转移客户端从哨兵那边获取到的新 Master 地址会自动变化。以 Java 的 Jedis 客户端为例之前的连接方式是new Jedis(10.0.0.10, 6379)哨兵模式下要改成SetString sentinels new HashSet(); sentinels.add(10.0.0.10:26379); sentinels.add(10.0.0.11:26379); sentinels.add(10.0.0.12:26379); JedisPool pool new JedisPool(new JedisPoolConfig(), mymaster, sentinels);注意这里的第二个参数是哨兵配置中定义的 master 名字必须保持一致。Jedis 内部会维护一个 Master 地址的订阅监听切换发生时能快速感知。Spring Boot 整合 Redis 时Spring Data Redis 的配置也支持通过哨兵属性配置哨兵列表和 master 名称。我在实际项目中还遇到过一种情况客户端连哨兵询问 Master 地址时哨兵给了新 Master但客户端自己缓存了旧地址导致读写一直失败。Jedis 自带的 Master 切换监听能主动感知而很多自定义的简化客户端就不会处理这种动态改变需要你在业务代码里自己实现地址刷新逻辑。这也是为什么我建议优先用成熟的 Redis 客户端库而不是自己封装一套。5. 故障转移实测把主节点杀了会怎样5.1 模拟主节点宕机理论说了再多不如亲手杀一次主节点。这里我模拟的是最狠的场景直接停掉 Master 上的 Redis 服务。在 10.0.0.10 上执行redis-cli -a your-strong-password shutdown nosave然后立刻在任意一台哨兵机器上观察哨兵日志关键日志大致会按下面顺序出现sdown master mymaster 10.0.0.10 6379 odown master mymaster 10.0.0.10 6379 #quorum 2/2 try-failover master mymaster 10.0.0.10 6379 switch-master mymaster 10.0.0.10 6379 10.0.0.11 6379 select-slave master mymaster 10.0.0.11 6379sdown是主观下线odown是客观下线quorum 2/2 表示两台哨兵都确认了。随后哨兵 leader 开始执行故障转移switch-master这行是核心它告诉整个系统Master 已经从 10.0.0.10 切换到了 10.0.0.11。整个流程从宕机到切换完成我实测下来基本在 10 秒左右符合 down-after-milliseconds 5000 加 failover-timeout 15000 的预期。其中很大一部分时间是等待 5 秒的下线确认期剩下的时间花在选举和配置分发上。5.2 原理拆解三个阶段的内部协议很多人面试喜欢被问哨兵原理这里我用大白话把三个阶段讲透。第一阶段是主观下线。每个 Sentinel 持续向 Master 发送 PING超过 down-after-milliseconds 没收到有效回复就在本地把 Master 标记为主观下线。注意这只代表单个哨兵的判断。第二阶段是客观下线。标记为主观下线的那个哨兵会把判断发给其他哨兵如果收到足够多的确认也就是 quorum 数量的哨兵都认为它下线就升级为客观下线。这一步的本质是排除单点误判避免某台哨兵跟主节点之间的网络抖动导致整个集群误切换。第三阶段是领导者选举和故障转移。客观下线确定后哨兵集群内部需要选出一个 leader 来执行故障转移这个选举机制叫 Raft本质上就是每个哨兵投自己一票先获得多数票支持的哨兵胜出成为 leader。拿到领导权的哨兵会从候选的从节点列表里选一个当新主优先级检测顺序依次是被标记为离线的从节点直接淘汰、跳过运行状态不健康的节点、选择复制偏移量最大的从节点、最后选择 runid 最小的节点作为兜底。这里我补充一点为什么优先选复制偏移量大的节点因为复制偏移量越大说明这个从节点从旧主那里同步到的数据越完整丢失的数据越少。选它当新主整体数据损失最小。这是哨兵机制设计里非常务实的一处设计。5.3 切换后的数据一致性和从节点重指向故障转移完成后剩下的两个从节点会自动重新指向新的 Master也就是 10.0.0.11。这时去任一从节点执行 INFO replication应该能看到 role 字段从原来的 master 或 slave 变成了指向新主的状态。这里要特别提醒一个数据一致性问题。由于 Redis 主从复制是异步的旧主在宕机前最后几秒里写入的数据可能还没来得及同步到任何一个从节点。故障转移之后这些数据就永久丢失了。哨兵解决的是可用性问题不是数据零丢失问题。如果业务对数据完整性有强要求需要在上层做降级处理或者接受这个极短窗口的丢失这也是所有使用 Redis 做主存储的场景必须想清楚的架构取舍。旧主恢复后它会发现自己已经不是主节点了哨兵会引导它以从节点身份重新加入集群并同步新主的数据。这个自动降级和重新接入的过程是哨兵协议设计里另一个让人省心的地方。不过有个细节点旧主连接新主也是要通过 masterauth 认证的所以前面强调过的 requirepass 和 masterauth 必须保持一致否则旧主恢复后无法完成重新同步。6. 常见故障排查与避坑实录6.1 哨兵不自动切换查这几个点哨兵配备了但故障发生后迟迟不切换这种问题我在社区里见过太多次我自己也排查过好几轮。按优先级整理了一份排查清单第一看 quorum 是否合理。如果三台哨兵里已经挂了一台quorum 还配了 3那么永远凑不齐多数派客观下线判定永远不会达成。quorum 的本质是最少需要多少个哨兵确认而不是哨兵总数这点必须理解。第二检查 Sentinel 之间的心跳通信。哨兵之间默认每 2 秒互相发送 PING同时通过发布订阅频道交换配置信息和状态。如果三台哨兵之间网络不通它们各自都只知道自己的判断无法形成共识。排查命令就是前面写的sentinel sentinels mymaster。第三确认 down-after-milliseconds 是否过大。如果你把这个值设成了 60 秒那么 Master 真的挂了也要等 60 秒才会触发主观下线加上后续流程整体 RTO 可能超过 1 分钟。对线上业务而言这个时间太长了。第四查看 sentinel 日志里是否有-failover-abort之类的记录。我见过一种情况Master 在故障恢复后短暂存活哨兵刚要切换发现 Master 又活了于是自动中止当前切换流程。这种场景看着像“没切换”实际是哨兵判断不需要切换反而是正常的。6.2 脑裂到底是什么影响有多大脑裂这个现象在 Redis 哨兵架构里表现为网络分区导致旧主和新主同时存在但旧主依然接收客户端写入一段时间后网络恢复旧主转为从节点它在这期间接收到的写入数据全部被丢弃。为什么会有这个问题哨兵判定 Master 下线依据的是哨兵和 Master 之间的连通性但客户端和 Master 之间的网络可能依然是通的。在网络分区的那段时间里旧主不知道新主已经产生继续对外提供写入服务产生的数据又没同步到新主等到分区恢复旧主以从节点身份强制与对新主做全量同步这部分数据就像没存在过一样。缓解脑裂影响有三个手段。一是配置 min-slaves-to-write 和 min-slaves-max-lag让旧主的写入能力跟从节点同步健康度绑定当从节点数量不足或者复制延迟过大时直接拒绝写入。二是使用哨兵模式时客户端要设置合理的超时时间不要无限期重试写操作。三是在业务层面对关键写入做幂等和补偿处理这属于架构层面的兜底策略。这里要客观说明resign 场景下无法用 Redis 自身机制做到完全的零丢失脑裂窗口期内丢失数据的风险始终存在做技术选型时要有清醒认知。6.3 配置细节里那些“看着一样实际不一样”的坑配置细节的坑往往最隐蔽我挑几个最容易“抄错”的记录一下。先看 protected-mode。Redis 默认是 yes如果你只配置了sentinel monitor忘记显式配置 bind 或者把 protected-mode 放宽那么非本机的哨兵连接可能直接被拒绝表现为哨兵之间始终无法互相发现。但如果你完全关闭 protected-mode又有安全风险所以我的做法是用防火墙精确控制来源 IP而不是单纯设成 no。再看 sentinel auth-pass 和 redis requirepass 的关系。哨兵向 Master 发起连接时同样需要认证这个密码是从 sentinel.conf 里的 auth-pass 取的。如果你改了 Redis 的密码但忘了同步更新每个哨兵的 auth-pass故障转移时哨兵连新主都会失败整个高可用机制瞬间失效。这个坑我犯过一次改密码后三个哨兵全部掉线线上业务全停教训非常深刻。还有一个隐蔽点哨兵的配置文件名和 master 名称。Sentinel 启动后会在运行时重写自己的配置文件把最新状态落实进去。如果你手工修改了 sentinel.conf 里的 master 名称却没有重启哨兵它运行时用的还是旧名称。很多排查不出原因的问题本质是配置文件和运行内存不一致。6.4 日常巡检建议天天盯着哨兵日志看是不现实的我习惯把巡检沉淀成脚本。比如写一个简单的 Shell 脚本定时检查哨兵状态redis-cli -h 10.0.0.10 -p 26379 sentinel master mymaster | grep -A 2 num-slaves更实际的做法是配合监控系统把sentinel master返回的num-slaves、ok-slaves等指标拉出来做曲线。还要定期检查从节点的复制延迟当 master_link_status 持续不是 up 时立刻安排排查。我见过有人在主从断连了整整一周后才发现日志数量早已堆积如山这种巡检缺失比 Redis 本身出问题更让人头疼。生产环境里有一个经常被忽略的地方是备份策略。很多人觉得有从节点就不需要备份了但请记住如果从节点因为同步异常或者数据损坏出问题整个集群可能都没法恢复到某个时间点。所以我始终保留定时 RDB 备份并定期演练恢复流程。高可用不等于数据安全两者必须同时抓。7. 最后分享一点真实感受这套哨兵集群我前后搭过三回第一回全按教程来配置写得满满当当但根本没搞懂 down-after-milliseconds 和 quorum 之间的配合关系后来有一次误触发切换业务侧大面积重连才发现自己把 down-after-milliseconds 设成了 500 毫秒纯属自己吓自己。之后把参数逐步调稳再配合真实的故障演练才真正做到心里有数。我的建议是无论你现在用的是云厂商托管的 Redis 还是自建的都值得花半天时间在测试环境里把主节点杀掉、把网络断掉、把哨兵杀掉看看各自会触发什么行为。这些实验做完你对 Redis 高可用的理解会完全不一样再遇到线上诡异故障时至少能快速判断是网络问题、配置问题还是哨兵协议的边界场景。