Redis 哨兵这套东西,我最早接触是在一个电商促销活动前夜。当时主从架构跑得好好的,结果主节点凌晨挂了,运维手动切了四十分钟,老板在群里连发十几条消息。从那以后我就把哨兵当成了高可用方案的标配。这篇文章不讲教科书定义,只聊我在实际项目里怎么理解它、怎么部署、怎么踩坑、怎么排查。如果你正在维护一套 Redis 主从、或者准备上高可用方案,这篇内容应该能帮你少走不少弯路。1. 哨兵到底在解决什么问题1.1 主从架构的天然短板Redis 主从复制本身不复杂,配好replicaof就能跑。但它有个致命问题:主节点是单点。主节点一挂,写请求全部失败,从节点只能读不能写。更麻烦的是,客户端配置里写死的主节点地址不会自动变,就算你手动把某个从节点提升为主节点,所有客户端还得改配置重启。我见过不少团队的做法是写个脚本监控主节点,挂了就调REPLICAOF NO ONE提升从节点,然后挨个改客户端配置。这种方案在节点少、业务简单的时候能凑合用,但节点一多、客户端一多,脚本本身就变成了新的故障点。而且脚本判断主节点是否真的挂了,靠的是 ping 通不通,网络抖动一下就可能误判,触发不必要的切换。哨兵要解决的就是这几个问题:自动监控、自动判断、自动切换、自动通知客户端。它把原来需要人肉操作的流程全部自动化,而且通过多哨兵投票机制降低误判概率。1.2 哨兵的核心能力拆解哨兵不是一个单独的程序,它是一组运行在特殊模式下的 Redis 进程。每个哨兵进程做四件事:监控:持续检查主节点、从节点以及其他哨兵的状态通知:当被监控的实例出现问题时,通过 API 向管理员或其他程序发送通知自动故障转移:主节点不可用时,从从节点中选一个提升为主节点,并让其他从节点指向新主节点配置提供者:客户端连接哨兵集群,询问当前主节点地址,哨兵返回最新结果这四件事里,最核心的是故障转移和配置提供。故障转移决定了系统能不能自愈,配置提供决定了客户端能不能自动找到新主节点。1.3 为什么不用 Cluster 替代经常有人问,既然有 Redis Cluster,为什么还要用哨兵。这个问题我在不同项目里被问过很多次。Cluster 解决的是分片和水平扩展问题,哨兵解决的是单点高可用问题。两者定位不同。Cluster 的架构复杂度比哨兵高一个量级,数据分片、槽位迁移、跨槽事务限制,这些都会给开发和运维带来额外负担。如果你的数据量单机内存扛得住,QPS 单节点也能撑住,只是想要高可用,那哨兵是更轻量的选择。我个人的判断标准是:单机内存不超过 32G、QPS 不超过 8 万,优先考虑哨兵;超过这个量级再考虑 Cluster。另外,哨兵对客户端的侵入性更小。大部分 Redis 客户端都支持哨兵模式,只需要配置哨兵地址列表,客户端自己会去问主节点在哪。而 Cluster 需要客户端支持集群协议,一些老客户端或者简单封装可能不支持。2. 哨兵集群的部署与配置细节2.1 节点数量与奇数原则哨兵集群的节点数量必须是奇数,最少 3 个。这个规则背后的逻辑是投票机制。哨兵判断主节点是否客观下线,需要超过半数(quorum)的哨兵同意。3 个哨兵,quorum 设为 2,挂一个还能正常工作;2 个哨兵,quorum 设为 2,挂一个就废了,因为凑不够半数。我实际部署时一般用 3 个哨兵,分布在不同的物理机或虚拟机上。如果条件允许,最好分布在不同机架甚至不同可用区。哨兵之间通过发布订阅互相发现,它们不需要配置彼此地址,只要配置同一个主节点,就能自动找到对方。注意:哨兵节点本身不存储数据,资源消耗很低,1 核 1G 的机器就能跑。但不要和 Redis 数据节点部署在同一台机器上,否则机器挂了,数据和哨兵一起没,高可用就失去意义了。2.2 核心配置参数逐条解读哨兵的配置文件叫sentinel.conf,核心配置项不多,但每一项都影响故障转移的行为。我按重要性逐个说。# 监控的主节点,quorum 为 2 sentinel monitor mymaster 192.168.1.10 6379 2 # 主节点多久无响应算主观下线,单位毫秒 sentinel down-after-milliseconds mymaster 30000 # 故障转移时最多同时有几个从节点同步新主节点 sentinel parallel-syncs mymaster 1 # 故障转移超时时间 sentinel failover-timeout mymaster 180000 # 认证密码 sentinel auth-pass mymaster yourpasswordsentinel monitor里的 quorum 是判断客观下线所需的最少票数。注意,quorum 只用于判断下线,真正执行故障转移还需要选举出领头哨兵,领头哨兵需要获得多数哨兵的授权。所以 quorum 设为 2,但选举领头需要 3 个哨兵中的 2 个同意,这两个条件要分开理解。down-after-milliseconds这个值很关键。设太短,网络抖动就触发切换;设太长,故障恢复慢。我一般设 30000,也就是 30 秒。如果业务对恢复时间敏感,可以降到 10000 到 15000,但要确保网络足够稳定。parallel-syncs控制故障转移后,同时有多少个从节点去同步新主节点。设大了,新主节点压力大;设小了,从节点逐个同步,整体恢复慢。我一般设 1,稳一点。failover-timeout影响多个阶段:从节点晋升失败后的重试间隔、新主节点出现后等待其他从节点同步的时间等。默认 180000 也就是 3 分钟,大部分场景够用。2.3 部署实操步骤假设你有三台机器,IP 分别是 192.168.1.11、192.168.1.12、192.168.1.13,主节点在 192.168.1.10:6379。三台机器上都部署哨兵。第一步,在每台机器上创建配置目录:mkdir -p /etc/redis-sentinel第二步,写入配置文件。三台机器的配置基本一致,只有sentinel myid不同,但这个值哨兵启动时会自动生成,不用手动配。配置内容:port 26379 daemonize yes logfile "/var/log/redis-sentinel/sentinel.log" dir "/var/lib/redis-sentinel" sentinel monitor mymaster 192.168.1.10 6379 2 sentinel down-after-milliseconds mymaster 30000 sentinel parallel-syncs mymaster 1 sentinel failover-timeout mymaster 180000 sentinel auth-pass mymaster yourpassword第三步,启动哨兵:redis-sentinel /etc/redis-sentinel/sentinel.conf第四步,验证。连上任意一个哨兵,执行:redis-cli -p 26379 sentinel master mymaster sentinel sentinels mymastersentinel master mymaster会返回主节点信息,包括 ip、port、num-slaves、num-other-sentinels 等。sentinel sentinels mymaster会列出其他哨兵。如果 num-other-sentinels 是 2,说明三个哨兵互相发现了。2.4 客户端接入方式哨兵模式下,客户端不直接连主节点,而是连哨兵集群。以 Java 的 Jedis 为例:Set<String> sentinels = new HashSet<>(); sentinels.add("192.168.1.11:26379"); sentinels.add("192.168.1.12:26379"); sentinels.add("192.168.1.13:26379"); JedisSentinelPool pool = new JedisSentinelPool("mymaster", sentinels, "yourpassword"); Jedis jedis = pool.getResource(); jedis.set("key", "value");客户端启动时会连接哨兵,问mymaster的主节点在哪,然后连主节点。主节点切换后,客户端会收到哨兵的通知,重新获取主节点地址。不同客户端的实现细节不同,但核心逻辑一致。提示:客户端配置哨兵地址时,建议把三个哨兵都配上。只配一个的话,那个哨兵挂了,客户端就找不到新主节点了。3. 故障转移的完整流程与内部机制3.1 主观下线与客观下线哨兵判断一个节点是否下线,分两个阶段。第一阶段是主观下线(SDOWN),单个哨兵自己认为节点挂了。触发条件是:在down-after-milliseconds时间内,ping 没有收到有效回复。第二阶段是客观下线(ODOWN),多个哨兵都认为节点挂了。当一个哨兵把主节点标记为主观下线后,它会向其他哨兵发送SENTINEL is-master-down-by-addr命令询问。如果收到足够数量(达到 quorum)的哨兵回复也认为主节点下线,那么这个哨兵就把主节点标记为客观下线,触发故障转移流程。这里有个细节:主观下线和客观下线的判断标准不同。主观下线只看 ping 响应,客观下线看的是其他哨兵的反馈。这样设计是为了避免单个哨兵因为网络问题误判。3.2 领头哨兵的选举主节点被标记为客观下线后,多个哨兵都可能同时想执行故障转移。为了避免冲突,需要先选出一个领头哨兵。选举用的是 Raft 算法的简化版。每个想当领头的哨兵向其他哨兵发送请求,说自己要当领头,请求投票。其他哨兵如果还没投过票,就投给它。一个哨兵获得超过半数(注意是哨兵总数的一半以上,不是 quorum)的票,就成为领头,开始执行故障转移。如果一轮没选出来,等一个随机时间后重新选。这个随机等待时间很重要,它避免了多个哨兵同时竞选导致一直选不出来。我实际观察下来,三个哨兵的集群,通常几秒内就能选出领头。3.3 新主节点的挑选规则领头哨兵选出后,开始从从节点中挑一个提升为主节点。挑选规则按优先级排序:排除已经下线、断线时间过长的从节点选择replica-priority最高的从节点,这个值默认 100,设 0 表示永不晋升优先级相同,选择复制偏移量最大的,也就是数据最新的偏移量相同,选择 runid 最小的这个规则里,replica-priority是最容易被人忽略的。我遇到过一种情况:某台从节点配置比主节点差很多,但优先级没改,结果主节点挂了,哨兵把这台弱机器提升为主节点,性能直接崩了。后来我把那台机器的replica-priority设成 0,问题解决。3.4 切换后的数据一致性处理新主节点选出来后,领头哨兵会让其他从节点指向新主节点。这里有个数据一致性的问题:旧主节点可能还有部分写请求没同步到从节点,切换后这部分数据就丢了。Redis 的复制是异步的,哨兵切换无法保证零丢失。要降低丢失风险,可以配置min-replicas-to-write和min-replicas-max-lag,让主节点在从节点数量不足或延迟过大时拒绝写入。但这两个参数会影响可用性,需要根据业务容忍度权衡。我一般建议对数据一致性要求高的业务,在应用层做补偿,比如写操作记录到消息队列,切换后检查是否有未同步的数据。完全依赖 Redis 自身保证零丢失是不现实的。4. 常见故障场景与排查实录4.1 频繁切换的排查思路频繁切换是最常见的哨兵问题。表现是主节点反复切换,日志里能看到多次+switch-master事件。原因通常有三类:第一类是网络抖动。哨兵和主节点之间的网络不稳定,ping 偶尔超时,触发主观下线。排查方法是看哨兵日志里+sdown和-sdown的时间间隔,如果很短就恢复,基本是网络问题。解决办法是调大down-after-milliseconds,或者排查网络设备。第二类是主节点负载过高。主节点 CPU 跑满,处理 ping 请求变慢,超过down-after-milliseconds没响应。这种情况要看主节点的慢查询日志和 CPU 使用率。解决办法是优化慢查询,或者升级主节点配置。第三类是哨兵之间时钟不同步。哨兵判断下线依赖时间,如果哨兵所在机器时钟差异大,可能导致判断混乱。解决办法是配置 NTP 同步。4.2 切换后客户端连不上的问题主节点切换后,客户端应该自动连到新主节点。但实际中经常出现客户端连不上的情况。我总结了几种原因:客户端只配置了一个哨兵地址,那个哨兵恰好挂了客户端没有正确处理哨兵返回的新地址,缓存了旧地址新主节点的防火墙规则没放开,客户端连不上新主节点的密码和旧主节点不一致排查时先确认哨兵返回的主节点地址是否正确,用sentinel master mymaster看。然后从客户端机器上直接 telnet 新主节点的端口,确认网络通不通。最后检查客户端日志,看它有没有收到哨兵的通知。4.3 哨兵集群自身故障哨兵集群本身也可能出问题。比如三个哨兵挂了两个,剩下的一个无法完成故障转移,因为凑不够多数票。这时候系统就失去了自愈能力。还有一种情况是哨兵之间网络分区,分成两个部分,每部分都以为自己这边是多数,可能导致脑裂。不过哨兵的选举机制要求多数票,分区后只有包含多数哨兵的那部分能执行切换,另一部分不会乱动。预防哨兵集群故障的办法是部署足够多的哨兵节点,并且分布在不同故障域。我一般建议至少 3 个,重要业务用 5 个。4.4 常见问题速查表问题现象可能原因排查方法解决措施频繁切换网络抖动看 sdown 和恢复间隔调大 down-after-milliseconds频繁切换主节点负载高看 CPU 和慢查询优化查询或升级配置切换后连不上客户端配置问题看客户端日志配全哨兵地址切换后连不上防火墙telnet 新主节点放开防火墙规则无法切换哨兵数量不足看哨兵存活数恢复哨兵节点数据丢失异步复制看复制偏移量应用层补偿5. 生产环境的经验与优化建议5.1 监控指标要盯哪些哨兵本身提供的监控信息有限,我一般会额外采集这些指标:主节点切换次数,短时间内多次切换说明有问题哨兵数量,少于预期要告警主从复制延迟,延迟过大影响切换后的数据一致性哨兵与主节点的 ping 延迟,延迟高说明网络有问题这些指标可以通过哨兵的INFO命令获取,也可以用 Redis 的INFO replication看复制状态。我习惯用 Prometheus 加 redis_exporter 采集,配 Grafana 看板,切换事件直接告警到群里。5.2 切换演练怎么做哨兵配置好了不代表切换一定能成功。我建议定期做切换演练,手动 kill 主节点,观察哨兵是否正常切换,客户端是否自动重连。演练时要注意几点:选择业务低峰期,提前通知相关方,准备好回滚方案。演练后检查数据一致性,确认没有异常丢失。我一般一个季度做一次,每次都能发现一些配置或客户端的问题。5.3 参数调优的实际经验down-after-milliseconds我一般设 30000,但如果是内网环境且网络很稳,可以降到 10000。failover-timeout默认 180000 够用,如果切换慢可以适当调小。parallel-syncs设 1 最稳,从节点多且机器性能好可以设 2 到 3。还有一个容易被忽略的参数是sentinel deny-scripts-reconfig,默认 yes,禁止哨兵通过脚本修改配置。生产环境保持默认就好,不要开。5.4 哨兵与容器化环境的配合在容器环境里跑哨兵有几个坑。第一,哨兵的myid是自动生成的,容器重启后如果配置文件没持久化,myid会变,其他哨兵会认为这是一个新哨兵。解决办法是把配置目录挂载出来持久化。第二,容器 IP 可能变化,哨兵之间互相发现依赖 IP,IP 变了会导致发现失败。解决办法是用固定 IP 或者用 StatefulSet 保证网络标识稳定。第三,健康检查要配好,容器编排平台的健康检查如果太激进,可能误杀哨兵。我一般把健康检查的超时设得比down-after-milliseconds长。6. 哨兵方案的边界与替代思路哨兵不是万能的。它解决的是单主节点的高可用,不解决容量和性能问题。如果业务增长到单机扛不住,哨兵帮不了你,得上分片方案。另外,哨兵的故障转移有时间窗口,通常是几十秒。这个窗口内写请求会失败。如果业务完全不能接受写失败,需要在应用层做降级或重试。还有一种情况是跨机房部署。哨兵跨机房部署时,机房之间的网络延迟会影响判断。我一般建议哨兵和主节点在同一个机房,从节点可以跨机房。这样主节点故障时,同机房的哨兵能快速判断并切换。我在实际项目里用哨兵跑了三年多,经历过几次真实的主节点故障,切换都在 30 秒内完成,客户端自动重连,业务基本无感知。但也有一次因为客户端只配了一个哨兵地址,那个哨兵恰好和主节点在同一台机器上,机器挂了之后客户端找不到新主节点,手动介入才恢复。从那以后,我要求所有客户端必须配全哨兵地址,这个习惯一直保持到现在。
Redis 哨兵这套东西,我最早接触是在一个电商促销活动前夜。当时主从架构跑得好好的,结果主节点凌晨挂了,运维手动切了四十分钟,老板在群里连发十几条消息。从那以后我就把哨兵当成了高可用方案的标配。这篇文章不讲教科书定义,只聊我在实际项目里怎么理解它、怎么部署、怎么踩坑、怎么排查。如果你正在维护一套 Redis 主从、或者准备上高可用方案,这篇内容应该能帮你少走不少弯路。1. 哨兵到底在解决什么问题1.1 主从架构的天然短板Redis 主从复制本身不复杂,配好replicaof就能跑。但它有个致命问题:主节点是单点。主节点一挂,写请求全部失败,从节点只能读不能写。更麻烦的是,客户端配置里写死的主节点地址不会自动变,就算你手动把某个从节点提升为主节点,所有客户端还得改配置重启。我见过不少团队的做法是写个脚本监控主节点,挂了就调REPLICAOF NO ONE提升从节点,然后挨个改客户端配置。这种方案在节点少、业务简单的时候能凑合用,但节点一多、客户端一多,脚本本身就变成了新的故障点。而且脚本判断主节点是否真的挂了,靠的是 ping 通不通,网络抖动一下就可能误判,触发不必要的切换。哨兵要解决的就是这几个问题:自动监控、自动判断、自动切换、自动通知客户端。它把原来需要人肉操作的流程全部自动化,而且通过多哨兵投票机制降低误判概率。1.2 哨兵的核心能力拆解哨兵不是一个单独的程序,它是一组运行在特殊模式下的 Redis 进程。每个哨兵进程做四件事:监控:持续检查主节点、从节点以及其他哨兵的状态通知:当被监控的实例出现问题时,通过 API 向管理员或其他程序发送通知自动故障转移:主节点不可用时,从从节点中选一个提升为主节点,并让其他从节点指向新主节点配置提供者:客户端连接哨兵集群,询问当前主节点地址,哨兵返回最新结果这四件事里,最核心的是故障转移和配置提供。故障转移决定了系统能不能自愈,配置提供决定了客户端能不能自动找到新主节点。1.3 为什么不用 Cluster 替代经常有人问,既然有 Redis Cluster,为什么还要用哨兵。这个问题我在不同项目里被问过很多次。Cluster 解决的是分片和水平扩展问题,哨兵解决的是单点高可用问题。两者定位不同。Cluster 的架构复杂度比哨兵高一个量级,数据分片、槽位迁移、跨槽事务限制,这些都会给开发和运维带来额外负担。如果你的数据量单机内存扛得住,QPS 单节点也能撑住,只是想要高可用,那哨兵是更轻量的选择。我个人的判断标准是:单机内存不超过 32G、QPS 不超过 8 万,优先考虑哨兵;超过这个量级再考虑 Cluster。另外,哨兵对客户端的侵入性更小。大部分 Redis 客户端都支持哨兵模式,只需要配置哨兵地址列表,客户端自己会去问主节点在哪。而 Cluster 需要客户端支持集群协议,一些老客户端或者简单封装可能不支持。2. 哨兵集群的部署与配置细节2.1 节点数量与奇数原则哨兵集群的节点数量必须是奇数,最少 3 个。这个规则背后的逻辑是投票机制。哨兵判断主节点是否客观下线,需要超过半数(quorum)的哨兵同意。3 个哨兵,quorum 设为 2,挂一个还能正常工作;2 个哨兵,quorum 设为 2,挂一个就废了,因为凑不够半数。我实际部署时一般用 3 个哨兵,分布在不同的物理机或虚拟机上。如果条件允许,最好分布在不同机架甚至不同可用区。哨兵之间通过发布订阅互相发现,它们不需要配置彼此地址,只要配置同一个主节点,就能自动找到对方。注意:哨兵节点本身不存储数据,资源消耗很低,1 核 1G 的机器就能跑。但不要和 Redis 数据节点部署在同一台机器上,否则机器挂了,数据和哨兵一起没,高可用就失去意义了。2.2 核心配置参数逐条解读哨兵的配置文件叫sentinel.conf,核心配置项不多,但每一项都影响故障转移的行为。我按重要性逐个说。# 监控的主节点,quorum 为 2 sentinel monitor mymaster 192.168.1.10 6379 2 # 主节点多久无响应算主观下线,单位毫秒 sentinel down-after-milliseconds mymaster 30000 # 故障转移时最多同时有几个从节点同步新主节点 sentinel parallel-syncs mymaster 1 # 故障转移超时时间 sentinel failover-timeout mymaster 180000 # 认证密码 sentinel auth-pass mymaster yourpasswordsentinel monitor里的 quorum 是判断客观下线所需的最少票数。注意,quorum 只用于判断下线,真正执行故障转移还需要选举出领头哨兵,领头哨兵需要获得多数哨兵的授权。所以 quorum 设为 2,但选举领头需要 3 个哨兵中的 2 个同意,这两个条件要分开理解。down-after-milliseconds这个值很关键。设太短,网络抖动就触发切换;设太长,故障恢复慢。我一般设 30000,也就是 30 秒。如果业务对恢复时间敏感,可以降到 10000 到 15000,但要确保网络足够稳定。parallel-syncs控制故障转移后,同时有多少个从节点去同步新主节点。设大了,新主节点压力大;设小了,从节点逐个同步,整体恢复慢。我一般设 1,稳一点。failover-timeout影响多个阶段:从节点晋升失败后的重试间隔、新主节点出现后等待其他从节点同步的时间等。默认 180000 也就是 3 分钟,大部分场景够用。2.3 部署实操步骤假设你有三台机器,IP 分别是 192.168.1.11、192.168.1.12、192.168.1.13,主节点在 192.168.1.10:6379。三台机器上都部署哨兵。第一步,在每台机器上创建配置目录:mkdir -p /etc/redis-sentinel第二步,写入配置文件。三台机器的配置基本一致,只有sentinel myid不同,但这个值哨兵启动时会自动生成,不用手动配。配置内容:port 26379 daemonize yes logfile "/var/log/redis-sentinel/sentinel.log" dir "/var/lib/redis-sentinel" sentinel monitor mymaster 192.168.1.10 6379 2 sentinel down-after-milliseconds mymaster 30000 sentinel parallel-syncs mymaster 1 sentinel failover-timeout mymaster 180000 sentinel auth-pass mymaster yourpassword第三步,启动哨兵:redis-sentinel /etc/redis-sentinel/sentinel.conf第四步,验证。连上任意一个哨兵,执行:redis-cli -p 26379 sentinel master mymaster sentinel sentinels mymastersentinel master mymaster会返回主节点信息,包括 ip、port、num-slaves、num-other-sentinels 等。sentinel sentinels mymaster会列出其他哨兵。如果 num-other-sentinels 是 2,说明三个哨兵互相发现了。2.4 客户端接入方式哨兵模式下,客户端不直接连主节点,而是连哨兵集群。以 Java 的 Jedis 为例:Set<String> sentinels = new HashSet<>(); sentinels.add("192.168.1.11:26379"); sentinels.add("192.168.1.12:26379"); sentinels.add("192.168.1.13:26379"); JedisSentinelPool pool = new JedisSentinelPool("mymaster", sentinels, "yourpassword"); Jedis jedis = pool.getResource(); jedis.set("key", "value");客户端启动时会连接哨兵,问mymaster的主节点在哪,然后连主节点。主节点切换后,客户端会收到哨兵的通知,重新获取主节点地址。不同客户端的实现细节不同,但核心逻辑一致。提示:客户端配置哨兵地址时,建议把三个哨兵都配上。只配一个的话,那个哨兵挂了,客户端就找不到新主节点了。3. 故障转移的完整流程与内部机制3.1 主观下线与客观下线哨兵判断一个节点是否下线,分两个阶段。第一阶段是主观下线(SDOWN),单个哨兵自己认为节点挂了。触发条件是:在down-after-milliseconds时间内,ping 没有收到有效回复。第二阶段是客观下线(ODOWN),多个哨兵都认为节点挂了。当一个哨兵把主节点标记为主观下线后,它会向其他哨兵发送SENTINEL is-master-down-by-addr命令询问。如果收到足够数量(达到 quorum)的哨兵回复也认为主节点下线,那么这个哨兵就把主节点标记为客观下线,触发故障转移流程。这里有个细节:主观下线和客观下线的判断标准不同。主观下线只看 ping 响应,客观下线看的是其他哨兵的反馈。这样设计是为了避免单个哨兵因为网络问题误判。3.2 领头哨兵的选举主节点被标记为客观下线后,多个哨兵都可能同时想执行故障转移。为了避免冲突,需要先选出一个领头哨兵。选举用的是 Raft 算法的简化版。每个想当领头的哨兵向其他哨兵发送请求,说自己要当领头,请求投票。其他哨兵如果还没投过票,就投给它。一个哨兵获得超过半数(注意是哨兵总数的一半以上,不是 quorum)的票,就成为领头,开始执行故障转移。如果一轮没选出来,等一个随机时间后重新选。这个随机等待时间很重要,它避免了多个哨兵同时竞选导致一直选不出来。我实际观察下来,三个哨兵的集群,通常几秒内就能选出领头。3.3 新主节点的挑选规则领头哨兵选出后,开始从从节点中挑一个提升为主节点。挑选规则按优先级排序:排除已经下线、断线时间过长的从节点选择replica-priority最高的从节点,这个值默认 100,设 0 表示永不晋升优先级相同,选择复制偏移量最大的,也就是数据最新的偏移量相同,选择 runid 最小的这个规则里,replica-priority是最容易被人忽略的。我遇到过一种情况:某台从节点配置比主节点差很多,但优先级没改,结果主节点挂了,哨兵把这台弱机器提升为主节点,性能直接崩了。后来我把那台机器的replica-priority设成 0,问题解决。3.4 切换后的数据一致性处理新主节点选出来后,领头哨兵会让其他从节点指向新主节点。这里有个数据一致性的问题:旧主节点可能还有部分写请求没同步到从节点,切换后这部分数据就丢了。Redis 的复制是异步的,哨兵切换无法保证零丢失。要降低丢失风险,可以配置min-replicas-to-write和min-replicas-max-lag,让主节点在从节点数量不足或延迟过大时拒绝写入。但这两个参数会影响可用性,需要根据业务容忍度权衡。我一般建议对数据一致性要求高的业务,在应用层做补偿,比如写操作记录到消息队列,切换后检查是否有未同步的数据。完全依赖 Redis 自身保证零丢失是不现实的。4. 常见故障场景与排查实录4.1 频繁切换的排查思路频繁切换是最常见的哨兵问题。表现是主节点反复切换,日志里能看到多次+switch-master事件。原因通常有三类:第一类是网络抖动。哨兵和主节点之间的网络不稳定,ping 偶尔超时,触发主观下线。排查方法是看哨兵日志里+sdown和-sdown的时间间隔,如果很短就恢复,基本是网络问题。解决办法是调大down-after-milliseconds,或者排查网络设备。第二类是主节点负载过高。主节点 CPU 跑满,处理 ping 请求变慢,超过down-after-milliseconds没响应。这种情况要看主节点的慢查询日志和 CPU 使用率。解决办法是优化慢查询,或者升级主节点配置。第三类是哨兵之间时钟不同步。哨兵判断下线依赖时间,如果哨兵所在机器时钟差异大,可能导致判断混乱。解决办法是配置 NTP 同步。4.2 切换后客户端连不上的问题主节点切换后,客户端应该自动连到新主节点。但实际中经常出现客户端连不上的情况。我总结了几种原因:客户端只配置了一个哨兵地址,那个哨兵恰好挂了客户端没有正确处理哨兵返回的新地址,缓存了旧地址新主节点的防火墙规则没放开,客户端连不上新主节点的密码和旧主节点不一致排查时先确认哨兵返回的主节点地址是否正确,用sentinel master mymaster看。然后从客户端机器上直接 telnet 新主节点的端口,确认网络通不通。最后检查客户端日志,看它有没有收到哨兵的通知。4.3 哨兵集群自身故障哨兵集群本身也可能出问题。比如三个哨兵挂了两个,剩下的一个无法完成故障转移,因为凑不够多数票。这时候系统就失去了自愈能力。还有一种情况是哨兵之间网络分区,分成两个部分,每部分都以为自己这边是多数,可能导致脑裂。不过哨兵的选举机制要求多数票,分区后只有包含多数哨兵的那部分能执行切换,另一部分不会乱动。预防哨兵集群故障的办法是部署足够多的哨兵节点,并且分布在不同故障域。我一般建议至少 3 个,重要业务用 5 个。4.4 常见问题速查表问题现象可能原因排查方法解决措施频繁切换网络抖动看 sdown 和恢复间隔调大 down-after-milliseconds频繁切换主节点负载高看 CPU 和慢查询优化查询或升级配置切换后连不上客户端配置问题看客户端日志配全哨兵地址切换后连不上防火墙telnet 新主节点放开防火墙规则无法切换哨兵数量不足看哨兵存活数恢复哨兵节点数据丢失异步复制看复制偏移量应用层补偿5. 生产环境的经验与优化建议5.1 监控指标要盯哪些哨兵本身提供的监控信息有限,我一般会额外采集这些指标:主节点切换次数,短时间内多次切换说明有问题哨兵数量,少于预期要告警主从复制延迟,延迟过大影响切换后的数据一致性哨兵与主节点的 ping 延迟,延迟高说明网络有问题这些指标可以通过哨兵的INFO命令获取,也可以用 Redis 的INFO replication看复制状态。我习惯用 Prometheus 加 redis_exporter 采集,配 Grafana 看板,切换事件直接告警到群里。5.2 切换演练怎么做哨兵配置好了不代表切换一定能成功。我建议定期做切换演练,手动 kill 主节点,观察哨兵是否正常切换,客户端是否自动重连。演练时要注意几点:选择业务低峰期,提前通知相关方,准备好回滚方案。演练后检查数据一致性,确认没有异常丢失。我一般一个季度做一次,每次都能发现一些配置或客户端的问题。5.3 参数调优的实际经验down-after-milliseconds我一般设 30000,但如果是内网环境且网络很稳,可以降到 10000。failover-timeout默认 180000 够用,如果切换慢可以适当调小。parallel-syncs设 1 最稳,从节点多且机器性能好可以设 2 到 3。还有一个容易被忽略的参数是sentinel deny-scripts-reconfig,默认 yes,禁止哨兵通过脚本修改配置。生产环境保持默认就好,不要开。5.4 哨兵与容器化环境的配合在容器环境里跑哨兵有几个坑。第一,哨兵的myid是自动生成的,容器重启后如果配置文件没持久化,myid会变,其他哨兵会认为这是一个新哨兵。解决办法是把配置目录挂载出来持久化。第二,容器 IP 可能变化,哨兵之间互相发现依赖 IP,IP 变了会导致发现失败。解决办法是用固定 IP 或者用 StatefulSet 保证网络标识稳定。第三,健康检查要配好,容器编排平台的健康检查如果太激进,可能误杀哨兵。我一般把健康检查的超时设得比down-after-milliseconds长。6. 哨兵方案的边界与替代思路哨兵不是万能的。它解决的是单主节点的高可用,不解决容量和性能问题。如果业务增长到单机扛不住,哨兵帮不了你,得上分片方案。另外,哨兵的故障转移有时间窗口,通常是几十秒。这个窗口内写请求会失败。如果业务完全不能接受写失败,需要在应用层做降级或重试。还有一种情况是跨机房部署。哨兵跨机房部署时,机房之间的网络延迟会影响判断。我一般建议哨兵和主节点在同一个机房,从节点可以跨机房。这样主节点故障时,同机房的哨兵能快速判断并切换。我在实际项目里用哨兵跑了三年多,经历过几次真实的主节点故障,切换都在 30 秒内完成,客户端自动重连,业务基本无感知。但也有一次因为客户端只配了一个哨兵地址,那个哨兵恰好和主节点在同一台机器上,机器挂了之后客户端找不到新主节点,手动介入才恢复。从那以后,我要求所有客户端必须配全哨兵地址,这个习惯一直保持到现在。