Redis 哨兵模式这件事,说简单也简单,说坑多也是真的坑多。我见过太多团队在主从复制搭完之后觉得“高可用已经搞定了”,结果真到主节点挂掉的那一刻,发现哨兵压根没切过来,或者切过来了但客户端还连着一个已经死掉的旧主。这篇文章就把我在 Linux 环境下配置 Redis 哨兵模式的完整过程拆开讲清楚,从为什么需要哨兵、节点规划、配置文件逐项拆解,到故障切换验证和踩过的坑,全部摊开来说。适合已经会用 Redis 基本命令、但还没在生产环境真正跑过哨兵的同学,也适合那些配过一遍但心里没底、想搞清楚每个参数到底在干什么的同行。
1. 先想清楚:哨兵到底在解决什么问题
1.1 主从复制留下的那个缺口
单机 Redis 的问题很直白:进程一挂,数据虽然在磁盘上,但服务断了。于是大家都会做主从复制,一个主节点负责写,若干个从节点同步数据,读请求可以分摊到从节点上。这套结构解决了两件事——数据有了冗余副本,读性能可以横向扩展。
但它留下了一个致命缺口:主节点挂了之后,没有人自动把从节点提升为新的主节点。你得半夜爬起来,手动执行REPLICAOF NO ONE把某个从节点扶正,再挨个改其他从节点和客户端的连接地址。这个过程短则几分钟,长则半小时,业务侧感受到的就是一段不可控的写入中断。
哨兵(Sentinel)就是来填这个缺口的。它是一套独立的进程,不存业务数据,专门干三件事:监控(持续检查主从节点是否存活)、通知(某个节点出问题时通过 API 告知外界)、自动故障转移(主节点确认失联后,选举一个从节点上位,并让其余从节点改挂到新主上)。客户端只要连哨兵集群而不是直连主节点,就能在切换后自动拿到新主地址。
这里有个概念要先厘清:哨兵不是 Redis 的一个配置开关,它是独立运行的进程,用的是 Redis 自带的redis-sentinel可执行文件(本质上和redis-server是同一个二进制,只是启动时带了哨兵模式参数)。很多人第一次配的时候以为在主节点配置文件里加几行就行,这是最常见的误解。
1.2 为什么哨兵必须部署奇数个
哨兵之间靠投票来决定“主节点是不是真的挂了”以及“谁来执行切换”。这就涉及两个阈值:主观下线(单个哨兵自己觉得节点不通了)和客观下线(足够数量的哨兵都认为它不通了)。
客观下线的判定依赖quorum参数,比如 3 个哨兵、quorum 设为 2,意味着至少 2 个哨兵都报告主节点失联,才认定为客观下线。而真正执行故障转移还需要选举出一个领头哨兵,这个选举要求获得多数派(majority)的授权,3 个哨兵需要 2 票,5 个需要 3 票。
所以哨兵数量必须是奇数,且至少 3 个。2 个哨兵的集群在挂掉 1 个之后就凑不齐多数派,无法完成选举;4 个哨兵虽然能容忍 1 个故障,但多数派是 3,容错能力和 3 个一样,白白多花资源。这就是“奇数部署”背后的数学逻辑,不是玄学。
注意:哨兵节点要分散在不同的物理机或虚拟机上。如果 3 个哨兵挤在同一台机器,机器一宕全部玩完,等于没做高可用。这是新手最容易犯的错。
1.3 一套典型的小规模部署规划
假设我们有三台 Linux 服务器,规划如下(IP 用示例地址代替,实际部署时替换成你自己的):
| 角色 | 主机标识 | 端口 | 说明 |
|---|---|---|---|
| Redis 主节点 | node-a | 6379 | 负责写 |
| Redis 从节点 | node-b | 6379 | 同步主节点 |
| Redis 从节点 | node-c | 6379 | 同步主节点 |
| 哨兵 1 | node-a | 26379 | 监控 |
| 哨兵 2 | node-b | 26379 | 监控 |
| 哨兵 3 | node-c | 26379 | 监控 |
主从节点和哨兵混布在同一批机器上是小规模场景的常见做法,哨兵进程本身资源占用很低(内存几十 MB 级别),不会和 Redis 抢太多资源。但要注意哨兵的默认端口是26379,和 Redis 的 6379 区分开,别配串了。
2. 主从复制:哨兵的地基必须先打牢
2.1 主节点配置的关键几行
哨兵的所有动作都建立在主从复制正常工作的前提上。如果主从本身就没同步好,哨兵切过去也是切了个空壳。先看主节点redis.conf里必须确认的配置:
# 绑定地址,生产环境建议绑定内网地址而非 0.0.0.0 bind 0.0.0.0 # 保护模式,绑定了具体地址后可关闭,否则保持 yes 并配好密码 protected-mode no # 端口 port 6379 # 后台运行 daemonize yes # 日志文件 logfile "/var/log/redis/redis-6379.log" # 数据目录 dir /var/lib/redis/6379 # 设置访问密码,哨兵和从节点都要用 requirepass YourStrongPassword # 主节点认证密码(Redis 5 之后用于主从认证) masterauth YourStrongPassword这里有个细节值得展开:requirepass和masterauth为什么要设成一样?requirepass是别人连我时需要的密码,masterauth是我作为从节点去连主节点时用的密码。在主从切换发生后,原来的从节点会变成主节点,它需要用masterauth去连新的上游(如果有级联),同时又要用requirepass接受别人连接。两个设成一致,切换后角色互换才不会出认证问题。这是很多人切换后从节点连不上新主的原因之一。
2.2 从节点配置与复制的建立
从节点的配置和主节点大同小异,核心是加一行指向主节点:
# 从节点专属:指定上游主节点 replicaof node-a 6379 # 只读模式,防止误写到从节点 replica-read-only yes # 复制相关的密码 masterauth YourStrongPassword requirepass YourStrongPasswordreplica-read-only yes这行别省。从节点默认就是只读的,但显式写出来是一种防御——万一有人图省事把它改成 no,往从节点写数据会造成主从数据不一致,而且这种不一致在哨兵切换后会被放大。
配置改完,先启动主节点,再启动两个从节点,然后在主节点上执行INFO replication看输出。正常应该看到role:master、connected_slaves:2,以及两个从节点的ip、port、state=online、offset信息。如果connected_slaves是 0,或者从节点状态是down,先别急着配哨兵,把复制链路修通再说。
2.3 验证复制真的在跑
光看状态不够,得实际写一条数据验证。在主节点执行:
redis-cli -a YourStrongPassword -p 6379 set smoke:test "hello-sentinel"然后到两个从节点上分别执行:
redis-cli -a YourStrongPassword -p 6379 get smoke:test两边都能读到hello-sentinel,说明复制链路是通的。这一步看着简单,但它是后面所有故障转移验证的基准——如果现在复制就不通,切换后新主上的数据一定是残缺的。
实操心得:我习惯在搭哨兵之前,先手动模拟一次“从节点提升”。在某个从节点上执行
REPLICAOF NO ONE,看它能不能独立对外服务,再执行REPLICAOF node-a 6379挂回去。这个动作能提前暴露权限、网络、配置上的问题,比等到真故障时才发现要强得多。
3. 哨兵配置文件逐项拆解
3.1 最小可用配置长什么样
哨兵的配置文件通常叫sentinel.conf,三个哨兵节点上的内容基本一致,只有少数几项因机器而异。先给一份能直接跑起来的最小配置:
# 哨兵端口,默认 26379 port 26379 # 后台运行 daemonize yes # 日志 logfile "/var/log/redis/sentinel-26379.log" # 工作目录,哨兵会在这里写状态文件 dir /var/lib/redis/sentinel # 监控的主节点,格式:sentinel monitor <主节点名> <IP> <端口> <quorum> sentinel monitor mymaster node-a 6379 2 # 主节点和哨兵的认证密码 sentinel auth-pass mymaster YourStrongPassword # 主观下线判定时间(毫秒) sentinel down-after-milliseconds mymaster 30000 # 故障转移时同时重新配置的从节点数量 sentinel parallel-syncs mymaster 1 # 故障转移超时时间 sentinel failover-timeout mymaster 180000sentinel monitor这一行是整个配置的灵魂。mymaster是给这个主节点起的逻辑名字,客户端连接哨兵时要用这个名字来查询主节点地址,所以三个哨兵必须用完全一致的名字。后面的2就是 quorum,前面讲过,3 个哨兵配 2 是合理的。
3.2 那几个时间参数到底怎么定
sentinel down-after-milliseconds设成 30000(30 秒)是很多团队的默认选择,但它的取值需要结合业务容忍度来算。这个参数的意思是:哨兵连续 30 秒 ping 不通主节点,才把它标记为主观下线。设太短,网络抖动一下就可能触发误判;设太长,真故障时切换延迟大。
我的经验是:先测一下你环境里正常的网络 RTT 和 Redis 的响应延迟,把阈值设在正常延迟的几十倍以上。内网环境 RTT 通常不到 1ms,30 秒已经非常宽松,能过滤掉绝大多数瞬时抖动。如果业务对中断极其敏感,可以压到 10 秒,但要确保网络质量足够稳定。
sentinel failover-timeout设 180000(3 分钟)控制的是故障转移各阶段的总超时,包括选举、提升从节点、重新配置其他从节点等。这个值不宜太小,因为切换过程中涉及多次状态同步,网络慢的环境下 1 分钟可能不够。3 分钟是个比较稳妥的起点。
sentinel parallel-syncs设为 1 意味着故障转移后,从节点是逐个去同步新主的,而不是一拥而上。这样做的原因是:新主刚上任,如果多个从节点同时发起全量同步,新主的 IO 和网络会被打满,反而拖慢整个恢复过程。设成 1 最保守,恢复慢一点但稳;从节点多、数据量小的场景可以适当调大。
3.3 密码认证这一块别漏
sentinel auth-pass mymaster YourStrongPassword这行必须和主从节点的requirepass对上。哨兵需要用它去连主节点和从节点做健康检查,密码不对的话,哨兵会一直报NOAUTH错误,监控形同虚设。
这里有个容易忽略的点:如果你的 Redis 6 及以上版本用了 ACL 用户体系,认证方式会变成sentinel auth-user加sentinel auth-pass的组合。老版本只有auth-pass。配置前先确认你的 Redis 版本,别照抄了不匹配的配置。
注意:三个哨兵的配置文件里,
sentinel monitor指向的必须是同一个主节点地址。哨兵之间会自动发现彼此(通过主节点的__sentinel__:hello频道交换信息),你不需要手动配置哨兵之间的连接。这一点很多人不知道,以为要像集群那样配一堆节点地址,其实不用。
4. 启动哨兵并验证集群状态
4.1 启动顺序与启动方式
推荐先启动所有 Redis 主从节点,确认复制正常,再依次启动三个哨兵。启动命令有两种写法:
# 方式一:用 redis-sentinel 可执行文件 redis-sentinel /etc/redis/sentinel.conf # 方式二:用 redis-server 带 --sentinel 参数 redis-server /etc/redis/sentinel.conf --sentinel两种方式等价,redis-sentinel本质上就是redis-server --sentinel的软链接。我一般用第一种,语义更清晰。
启动后连上任意一个哨兵(端口 26379),执行INFO sentinel,重点看这几行:
redis-cli -p 26379 info sentinel输出里应该有sentinel_masters:1、master0:name=mymaster,status=ok,address=node-a:6379,slaves=2,sentinels=3。sentinels=3说明三个哨兵已经互相发现,集群成型了。如果这里是 1 或者 2,说明哨兵之间的自动发现还没完成,等几十秒再看,或者检查网络和密码配置。
4.2 用 SENTINEL 命令做健康检查
哨兵提供了一组SENTINEL开头的命令,日常运维非常有用:
# 查看主节点信息 redis-cli -p 26379 sentinel master mymaster # 查看所有从节点 redis-cli -p 26379 sentinel slaves mymaster # 查看其他哨兵 redis-cli -p 26379 sentinel sentinels mymaster # 查看主节点地址(客户端常用) redis-cli -p 26379 sentinel get-master-addr-by-name mymastersentinel master mymaster的输出里有个flags字段,正常应该是master。如果出现s_down或o_down,说明哨兵认为主节点有问题,得去查主节点状态。num-slaves和num-other-sentinels也要和预期对上。
4.3 客户端怎么连哨兵
这是配置哨兵时经常被忽略的一环:客户端不能直连主节点,必须通过哨兵获取主节点地址。以常见的客户端为例,连接参数从“主节点 IP + 端口”变成“哨兵地址列表 + 主节点名”。
以 Java 生态里常用的客户端为例,配置大致是这样(伪代码示意,具体类名依客户端库而定):
// 哨兵地址列表,不是主节点地址 List<String> sentinels = Arrays.asList( "node-a:26379", "node-b:26379", "node-c:26379" ); // 主节点逻辑名,必须和 sentinel monitor 里的一致 String masterName = "mymaster"; // 密码 String password = "YourStrongPassword";客户端连上哨兵后,会订阅哨兵的切换通知频道,主节点一变,客户端自动重连到新主。这就是哨兵模式对客户端透明的原因。如果你的客户端还在硬编码主节点 IP,那哨兵切了也白切,客户端照样连旧地址。
实操心得:切换验证时,一定要用真实客户端去测,而不是只用 redis-cli 连哨兵看地址。有些客户端库对哨兵通知的处理有延迟或 bug,只有真实业务流量才能暴露出来。
5. 故障转移实测:把主节点干掉看看
5.1 模拟主节点故障
配置得再漂亮,不实测都是纸上谈兵。最直接的验证方式是把主节点进程杀掉:
# 在主节点上找到 redis 进程并终止 redis-cli -p 6379 -a YourStrongPassword shutdown nosave用shutdown nosave而不是kill -9,是为了模拟一次“正常但突然”的退出。nosave表示不落盘直接退,更接近真实故障场景。
杀掉之后,盯着哨兵的日志看(/var/log/redis/sentinel-26379.log),应该能看到类似这样的过程:先是+sdown(主观下线),然后+odown(客观下线,说明凑够 quorum 了),接着+try-failover、+elected-leader、+selected-slave、+promoted-slave、+failover-end,最后+switch-master。这一串事件就是一次完整的故障转移流水账。
5.2 确认切换结果
等日志里出现+switch-master后,到任意哨兵上执行:
redis-cli -p 26379 sentinel get-master-addr-by-name mymaster返回的地址应该已经变成某个从节点的 IP 和端口。再到那个被提升的节点上执行INFO replication,确认role:master,并且另一个从节点已经挂到它下面(connected_slaves:1)。
数据层面也要验证:之前在旧主上写的smoke:test,在新主上应该还能读到。如果读不到,说明复制有延迟或者切换时数据没同步完,这是需要警惕的信号。
5.3 旧主恢复后会发生什么
把原来的主节点重新启动,它会以从节点的身份加入集群——哨兵会自动给它下发REPLICAOF命令,让它挂到新主下面。这个过程不需要人工干预,但要注意:旧主重新加入时会做一次全量同步,如果数据量大,这段时间新主的负载会上升。
验证方法:重启旧主后,在它上面执行INFO replication,role应该变成slave,master_host指向新主。如果它还是master,说明哨兵的自动重配置没生效,去查哨兵日志里有没有+convert-to-slave相关记录。
| 阶段 | 哨兵日志事件 | 预期状态 |
|---|---|---|
| 主节点失联 | +sdown | 单哨兵标记主观下线 |
| 达到 quorum | +odown | 多哨兵确认客观下线 |
| 选举领头 | +elected-leader | 选出执行切换的哨兵 |
| 提升从节点 | +promoted-slave | 某从节点变主 |
| 切换完成 | +switch-master | 全局主节点地址更新 |
| 旧主回归 | +convert-to-slave | 旧主降级为从 |
6. 踩过的坑与排查速查表
6.1 切换后客户端连不上新主
这是最高频的问题。原因通常有两个:一是客户端没配哨兵,还在硬编码旧主地址;二是新主的requirepass和客户端配置的密码不一致。排查时先确认客户端连接的是哨兵地址而非主节点地址,再用sentinel get-master-addr-by-name拿到新主地址,手动用客户端密码连一下新主,能连上就说明是客户端配置问题。
还有一种隐蔽情况:切换后新主的protected-mode是 yes 且没绑定具体地址,导致外部连不上。检查新主的bind和protected-mode配置,确保和切换前一致。
6.2 哨兵之间互相发现不了
INFO sentinel里sentinels数量少于预期,说明哨兵没组成集群。常见原因:三个哨兵的sentinel monitor里主节点名不一致(比如一个写mymaster,一个写master1),或者主节点密码配错导致哨兵连不上主节点、无法通过__sentinel__:hello频道交换信息。逐个检查配置文件的sentinel monitor行和auth-pass,基本能定位。
6.3 频繁误切换
如果日志里反复出现+sdown又-sdown,说明down-after-milliseconds设得太短,或者网络本身不稳定。先把阈值调大观察,同时排查网络层是否有丢包。另外,主节点如果因为执行了耗时的BGSAVE或大 key 操作导致短暂无响应,也可能被误判,这种情况要结合慢查询日志一起看。
6.4 故障转移卡住不完成
日志停在+try-failover之后没有下文,通常是failover-timeout太短,或者被选中的从节点数据太旧、同步太慢。可以适当调大failover-timeout,并检查从节点的master_link_status是否为up。如果某个从节点长期同步不上,哨兵可能选它做新主却提升失败,考虑把它从集群里摘掉。
| 现象 | 可能原因 | 排查动作 |
|---|---|---|
| 客户端连不上新主 | 未配哨兵/密码不符/保护模式 | 查客户端配置、手动连新主 |
| 哨兵数量不足 | 主节点名不一致/认证失败 | 核对 monitor 行和密码 |
| 频繁误切换 | 超时阈值过短/网络抖动 | 调大 down-after-milliseconds |
| 切换卡住 | 超时太短/从节点数据旧 | 调大 failover-timeout、查同步状态 |
| 旧主不降级 | 哨兵重配置失败 | 查 convert-to-slave 日志 |
6.5 一个容易被忽视的细节:状态文件
哨兵运行时会往dir指定的目录写一个sentinel.conf的运行时状态(配置会被哨兵自己改写,记录当前主节点是谁、有哪些从节点和哨兵)。这意味着你手动改的配置文件在运行中可能被覆盖。所以改配置前先停哨兵,改完再启动;或者用SENTINEL SET命令在运行时动态调整参数,避免直接编辑文件被覆盖。这个坑我在早期项目里踩过,改完配置重启发现改动没了,排查半天才反应过来是哨兵自己重写了文件。
7. 生产环境还要补的几件事
7.1 哨兵进程的守护与开机自启
哨兵进程挂了,监控就断了,所以必须用系统级的进程管理来守护。常见的做法是写一个 systemd 服务单元,配置Restart=always,让进程异常退出后自动拉起。三个哨兵节点都要配,别只配一个。开机自启也一并加上,避免机器重启后哨兵没起来、集群处于“无监控”状态。
7.2 监控告警要覆盖哨兵本身
很多人只监控 Redis 主从,忘了监控哨兵。哨兵的关键指标包括:sentinel_masters数量、各主节点的flags状态、sentinels数量是否达标。一旦某个哨兵的sentinels数量掉下来,说明集群完整性受损,要立即告警。另外,哨兵日志里的+switch-master事件应该触发通知,让团队知道发生了一次切换,便于事后复盘。
7.3 数据一致性的边界要心里有数
哨兵模式用的是异步复制,主节点写入后不等从节点确认就返回成功。这意味着故障转移时,如果旧主上有少量数据还没同步到从节点,这部分数据会丢失。哨兵能保证服务可用性,但保证不了零数据丢失。对数据一致性要求极高的场景,需要评估是否接受这个边界,或者在上层做补偿。这一点在方案评审时一定要说清楚,别让业务方误以为哨兵等于数据不丢。
7.4 定期演练切换
配置好不等于永远好用。网络拓扑变了、机器换了、密码改了,都可能让哨兵失效。我建议至少每季度做一次故障转移演练,用SENTINEL FAILOVER mymaster命令手动触发一次切换(这个命令会强制发起故障转移,不需要真的杀进程),验证整个链路是否还能正常工作。演练完记得确认旧主正确降级、客户端正常重连。
# 手动触发一次故障转移,用于演练 redis-cli -p 26379 sentinel failover mymaster这个命令比杀进程温和,适合在业务低峰期做例行检查。执行后同样观察日志里的+switch-master事件,确认切换成功。
我个人在实际操作中的体会是,哨兵模式的配置本身并不复杂,真正花时间的是验证和边界确认——把切换流程跑通、把客户端改造到位、把数据丢失的边界和业务方对齐。配置文件抄一遍十分钟,但这些验证工作可能要花上一两天。别跳过验证直接上生产,哨兵这东西,平时安安静静,出事的时候才知道配得对不对。