我印象里凡是搞过Redis高可用的人,基本都绕不开哨兵这关。Sentinel作为Redis官方的高可用方案,本身设计得很巧妙,但启动和配置的过程,特别是第一次上手时,能踩的坑真不少。这篇文章就是把我自己从“配置写错导致哨兵直接起不来”到“主从切换永远触发不了”这一路上的实际问题,按主题拆开,做成一份可以随时翻的避坑记录。如果你正在搭哨兵环境,或者已经被哨兵折腾得没脾气,这份内容应该能帮上忙。
1. 哨兵机制与启动前的核心认知
1.1 哨兵到底在解决什么问题
先花点篇幅把哨兵的角色理顺。Redis哨兵的核心职责是监控、通知、自动故障转移和配置中心。一句话说,就是当主节点挂掉时,能自动从从节点里挑一台顶上,对客户端来说不用手动改连接地址。
这个概念不难理解,但很多人首次上手时,脑子里只有“哨兵可以自动切换”这个模糊印象,忽略了它依赖的底层机制。我见过不少开发者直接照着别人博客抄一份sentinel.conf,启动后发现主库下线了,哨兵却没反应,然后开始怀疑人生。问题很可能就出在down-after-milliseconds设得太长,或者仲裁quorum值设得太随意。
这里有个很关键的类比:哨兵就像小区门口的保安,他不是每时每刻都能发现异常,而是按照固定频率去巡楼。sentinel monitor配置里指定的主节点名、IP、端口和仲裁数,决定了他“盯哪栋楼、几个人一起说事才算出事”。
哨兵之间是通过Redis的发布订阅机制互相通信的,仲裁数quorum表示至少要有多少个哨兵认为主节点不可用,才会触发主观下线到客观下线的状态转换。这里特别容易忽略的是,quorum并不是你写多少就一定能达到多少,它取决于你的哨兵实例总数。如果配置了3个哨兵,quorum设为2,那么两个哨兵达成一致才会切换;如果只部署了1个哨兵,quorum就算写了2也毫无意义。
1.2 启动前必须理清的角色关系
我踩的第一个坑,就是把哨兵当成“一个独立工具”,没有把它和Redis实例之间的角色关系理清楚。实际上,哨兵节点和Redis节点是独立的进程,但它们之间的配置是有联动的。
比如你的主节点设置了密码,那么哨兵配置里必须同步设置对应的认证信息。具体来说,sentinel auth-pass <master-name> <password>是针对主节点的;而requirepass和masterauth,则分别对应Redis实例本身的访问密码和主从复制时的认证密码。真的有太多人在主库设置了requirepass,却忘了在从库配masterauth,结果主从复制日志里一直刷MASTER aborted replication。
另外,哨兵自己也是Redis实例,这意味着它同样受protected-mode、bind等配置影响。默认情况下,如果你的哨兵监听的是公网IP或0.0.0.0,而且没有设置密码,它可能会拒绝响应命令——这就是一启动“看起来活着但什么都没干”的经典原因之一。
所以在动手改配置之前,先把拓扑图画清楚:哪台机器上跑主库,哪台跑从库,哪几台跑哨兵;哪些实例需要密码,主从复制的认证怎么传,哨兵和客户端之间怎么交互。这个环节省了,后面全是坑。
2. 配置文件层面的高频踩坑点
2.1 配置项名称与格式错误
配置文件是踩坑的主战场。Redis的配置项都是小写字母+中划线的形式,哨兵特有的指令以sentinel开头。最常见的错误是大小写混用,比如把Sentinel monitor写成了大写S,进程直接拒绝启动。
另外就是格式顺序问题。sentinel monitor <master-name> <ip> <port> <quorum>里,master-name是你自己随便起的逻辑名,但注意这个名字不能带空格,最好也不要带中文。IP地址这一项在较新的Redis版本里支持主机名,但为了稳定,建议直接用IPv4地址。
还有一个很容易踩的格式坑:sentinel down-after-milliseconds <master-name> <milliseconds>。有些博客示例会写成sentinel down-after-milliseconds mymaster 30000,这是对的。但如果你把数字里的单位字母也写进去,比如30000ms,启动时同样会报错。别笑,我真的见过有人这么干,然后查了半天才发现是单位后缀的问题。
哨兵启动时会严格校验配置文件里的每一行,任何一条格式不合法,整个进程都可能起不来,而且报错信息未必直接指向出错的那一行。所以修改配置文件后,建议先在前台启动一遍(不加载daemonize配置),如果报错,它会直接打印出哪一行有问题。
2.2 密码与权限配置的“连锁反应”
哨兵和Redis之间的密码认证关系,是我见过最混乱的部分。核心逻辑是:
- Redis实例自身的
requirepass,控制外部访问Redis的权限。 - 从库如果要从主库同步数据,需要配置
masterauth,否则复制建立不了。 - 哨兵要接管主库,必须配置
sentinel auth-pass <master-name> <password>,这个密码通常是主库的requirepass。 - 哨兵之间互相通信时,同样需要认证,这靠的是Redis实例本身的requirepass或哨兵配置文件里的auth-pass。
我实际遇到过一种情况:主库和从库都设置了requirepass,从库的masterauth也写了,replication正常,但哨兵一启动,立刻在主库日志里看到NOAUTH Authentication required。原因就是哨兵配置里漏了sentinel auth-pass。它尝试向主节点发送INFO命令时被拒,无法获取主节点状态,自然也不会触发任何故障转移。
还有一个冷门但致命的权限问题:如果从库在启动时通过replicaof配置跟随主库,而这个配置是在主库出现故障后才补上去的,那么当主库恢复并变成从库时,新提升的主库可能没有正确的masterauth配置,导致复制链断裂。这类问题通常不会在启动那一刻暴露,而是会在故障切换后突然冒出来。
2.3 保护模式与绑定IP的深坑
protected-mode是Redis 3.2以后引入的机制,目的是防止默认配置下实例暴露到公网。它导致的启动问题,往往是“哨兵看起来在运行,但外部客户端连不上”。
具体表现为:哨兵日志正常,没有报错,但你从另一台机器执行redis-cli -p 26379 -a pass sentinel master mymaster,返回(error) DENIED Redis is running in protected mode。这就是因为哨兵进程没有设置密码,又监听了非本地回环地址,于是它默认拒绝外部的非认证请求。
解决的常规办法有两个:一是给哨兵也设置requirepass,但注意这会导致客户端和哨兵通信时也必须带密码,所有连接调用都需要改;二是在配置文件里显式加一行protected-mode no。从安全性角度讲,我更建议加密码而不是关闭保护模式。
但这里有个隐藏的坑:当你在哨兵配置里写下sentinel auth-pass时,其实它针对的是被监控的Redis主节点,不是哨兵自己。哨兵进程自身的保护模式,还是由自己的protected-mode决定。很多人在被保护模式拦截时,误以为加上sentinel auth-pass就能解决,结果根本没生效。
3. 启动过程与运行期的实操经验
3.1 从“完全起不来”到“正常启动”的完整流程
我在本地复现了一套最小化的哨兵环境,三个节点:一个主库(6379)、一个从库(6380)、一个哨兵(26379)。配置文件的完整示例大致如下。
主库的redis.conf核心段:
bind 0.0.0.0 port 6379 daemonize yes requirepass YourPass123从库的redis.conf核心段:
bind 0.0.0.0 port 6380 daemonize yes replicaof 127.0.0.1 6379 masterauth YourPass123 requirepass YourPass123哨兵的sentinel.conf核心段:
bind 0.0.0.0 port 26379 daemonize yes protected-mode no sentinel monitor mymaster 127.0.0.1 6379 1 sentinel auth-pass mymaster YourPass123 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 10000 sentinel parallel-syncs mymaster 1这里有两种启动方式。一种是直接执行:
redis-sentinel /path/to/sentinel.conf另一种是:
redis-server /path/to/sentinel.conf --sentinel两种方式等价,但有一个区别要注意:redis-server的daemonize是在配置文件里设置的,如果为yes,它会在后台运行;但如果你在前台运行redis-sentinel,日志会直接打到控制台,便于排查启动报错。
哨兵正常启动后,日志里会看到类似这样的行:
+monitor master mymaster 127.0.0.1 6379 +slave slave 127.0.0.1:6380 127.0.0.1 6380看到+monitor和+slave,基本就能确认哨兵已经成功监控了主从拓扑。如果只看到+monitor而没有+slave,通常是从库的复制关系没建好。
3.2 明明“活着”却什么都不干的排查
这是哨兵启动后最常见的困惑:日志没报错、进程在跑、客户端也能通过哨兵拿到主节点地址,但模拟故障后,它死活不切换。
第一步是确认日志。哨兵运行中会输出大量带+或-前缀的日志条目。比如从节点掉线,会有+sdown slave;主节点掉线,会有+sdown master。如果连sdown都没出现,说明哨兵根本没察觉主节点异常。这时候去检查down-after-milliseconds,这个值表示“心跳多久没收到才认为节点挂了”,默认是30秒。如果你测试时等不到30秒就着急,当然不会看到任何反应。
第二步是确认仲裁数。我在配置里写的是quorum为1,这用于测试是没问题的,因为只有一个哨兵实例,它自己就能做决定。但在生产环境,quorum设为1意味着“只要一个哨兵说主挂了就切换”,这在网络抖动时极其危险,很可能造成脑裂。生产环境至少3个哨兵,quorum设为2。
第三步是观察哨兵日志里是否有+odown(客观下线)。只有当绝大多数哨兵达成一致,才会进入故障转移流程。如果你只起了1个哨兵,但quorum写成2,那么永远也不会进入odown,自然也不会切换。相信我,这个场景我实际踩到过,当时还以为是网段隔离问题,查了半天才发现是quorum配置不合理。
第四步是检查+failover-start日志。如果连这行都没有,说明哨兵虽然发起了客观下线,但仲裁后的决策被某些条件卡住了。比较常见的条件是sentinel failover-timeout设得太短,导致还没完成主库选择就超时了。也有一种情况是并行同步数parallel-syncs设得太大,新主库选拔时,大量从库同时向它同步,造成网络拥塞,故障转移迟迟无法收尾。
3.3 配置重写机制带来“灵异”现象
哨兵有一个特性,会让第一次接触的人觉得很“玄幻”:哨兵进程会自动重写自己的配置文件。
什么意思?比如你手写了sentinel monitor mymaster 127.0.0.1 6379 1,等哨兵跑了一段时间,你再看这个文件,可能会发现它变成:
sentinel monitor mymaster 127.0.0.1 6379 1 sentinel myid 0d7f9c2a1b... sentinel known-replica mymaster 127.0.0.1 6380 sentinel known-sentinel mymaster 127.0.0.1 26380 ...这些新增的内容是哨兵运行时自动记录并回写到配置文件里的。好处是哨兵之间能互相发现,坏处是如果你在生产环境手动编辑配置文件,哨兵可能在下次改动后把它的运行时状态覆盖进去,导致你的修改“消失了”。
我遇到过最迷惑的操作是:改完down-after-milliseconds后重启哨兵,发现改动没生效。后来才发现,哨兵退出时会把运行时的内存配置回写文件,覆盖了我手动改的那一行。解决办法是:修改配置文件前,先通过命令停机哨兵,改完再启动;或者修改后,让哨兵自动重写一次再确认内容。
另外,sentinel myid是哨兵的唯一ID,不要擅自复制或修改,它影响哨兵集群的选举。如果你把两个哨兵的myid设成一样,它们会互相认为对方是自己,导致选举异常,这是非常根因诡异的问题。
3.4 主服务切换后客户端如何感知
切换成功后,哨兵会把新的主节点信息通过发布订阅推给客户端。但前提是客户端实现了哨兵协议,比如Java的Jedis、Lettuce或Python的redis-py都有对应的Sentinel连接池。
这里有个容易踩的坑:客户端连接哨兵时,使用的master-name必须和你配置里的sentinel monitor的master-name完全一致。比如配置里叫mymaster,客户端写my-master,那么哨兵会返回(error) NO such master with that name,客户端自然无法拿到正确的主节点地址。
另一个容易被忽略的问题是:故障转移完成后,哨兵的配置会被重写,自动更新主节点和从节点的信息。但客户端如果持有旧的连接池,可能还会尝试向旧主库写入数据。解决思路是让客户端连接哨兵自动发现主节点,而不是自己在代码里写死IP。
我之前用客户端直连Redis主库,哨兵一切换,代码里那个IP就失效了,导致线上告警刷屏。改成连接哨兵的Sentinel连接池后,库挂了也能自动跟着切换,体验完全不一样。
4. 踩坑实录:典型报错与排查速查
4.1 常见启动报错对照表
我整理了一个速查表,基本覆盖了哨兵启动阶段曝过的大多数问题。
| 报错信息或现象 | 根因 | 解决方案 |
|---|---|---|
sentinel monitor后提示Unknown command | 配置项大小写写错或格式不对 | 检查拼写,必须是sentinel monitor且参数完整 |
| 进程起不来,日志无输出 | 配置文件无法写入或daemonize配置冲突 | 检查目录权限,用绝对路径;先在前台启动看报错 |
DENIED Redis is running in protected mode | 未设置密码且监听非回环地址 | 设置requirepass或显式配置protected-mode no |
日志里持续报-failover-abort | 主节点在切换期间被哨兵认为仍然存活 | 查看down-after-milliseconds是否设得太短;检查网络抖动 |
| 打开配置文件发现内容总被改写 | 哨兵自动回写运行时状态 | 先停机再改配置,改完启动后再次确认 |
从库复制日志一直报MASTER aborted replication | 从库没有配置masterauth | 在从库配置masterauth并与主库requirepass保持一致 |
| 哨兵能监控但不能故障转移 | quorum配置大于哨兵实例总数 | 检查quorum与哨兵节点数量之间的关系 |
4.2 一套实用的排查顺序
很多新手遇到哨兵异常后,第一反应是把哨兵进程杀掉重启,这个习惯其实很危险。哨兵自己会保存runtime状态,重启可能导致故障转移状态丢失,反而不利于定位问题。
我建议按这个顺序排查:
先确认Redis主从本身存活和复制正常。执行INFO replication,看主库的role:master和从库的connected_slaves,如果主从复制没建立好,哨兵再怎么配置也是白搭。
再确认哨兵能连通所有节点。用一个命令就能测:
redis-cli -p 26379 -a YourPass123 sentinel replicas mymaster如果返回不了从节点列表,说明哨兵对从节点的信息同步有问题,重点检查认证和网络。
接着看哨兵日志中的状态转换序列。正常情况是+sdown到+odown再到+failover-start。卡在哪一步,就去查那一步相关的配置。比如卡在+odown之前,多半是仲裁数达不到;卡在+failover-start之后,多半是故障转移超时或选举问题。
最后才考虑重启。重启前,先备份一份sentinel.conf,因为哨兵退出时可能会覆盖它。备份后用redis-server /path/to/sentinel.conf --sentinel方式前台启动,观察完整输出。
4.3 关于“持续更新”版本差异的提醒
既然标题写了“持续更新”,这篇内容其实也适用于讲一个概念:Redis的版本迭代,会对哨兵配置产生细微影响。
Redis 6.0以后,replicaof取代了slaveof,但兼容旧写法。从Redis 5.0开始,哨兵支持ACL,可以通过用户权限来限制命令,而不是只依赖requirepass。如果你使用ACL用户,那么哨兵配置里写明的密码需要和ACL用户密码一致,同时这个用户至少要有访问主节点和从节点的权限。
另外,从Redis 7.0开始,部分配置项行为有调整。比如down-after-milliseconds对主观下线的影响更严格,哨兵对时钟跳变也更敏感。建议部署前先阅读当前版本自带sentinel.conf示例文件,而不是拿着网上老教程照抄。
正是因为这个组件一直在迭代,所以踩坑记录本身就是活的。后面我会在遇到新问题后继续往这个主题里补,保持更新状态。
5. 留给后来人的几句提醒
这套哨兵配置和排查方法,几乎全是从事故里磨出来的。最后再分享几条我自己比较深的体会。
第一件:不要为了图方便在单台机器上跑多个哨兵进程。真实环境里,哨兵应该独立分布在不同的物理节点或至少不同机架上,否则一旦物理机宕机,你部署的所有哨兵同时失联,高可用就成了一张废纸。演示环境怎么玩都行,生产环境必须认真选点。
第二件:故障转移的触发条件里,网络抖动比进程崩溃更难排查。我自己就遇到过主节点负载短暂飙高,哨兵判定主观下线,触发了一次误切换,结果过了几秒主节点恢复,反而产生了无谓的复制风暴。后来我把down-after-milliseconds调大,才把这问题压住。这个值没有一个万能答案,要根据你业务的实际响应时间来调,而不是照抄默认值。
第三件:哨兵配置文件的修改,一定要在停机状态下操作。这句话值得再强调一次。在线直接编辑配置文件,大概率会被哨兵自动回写覆盖。改完配置以后,也建议用SENTINEL CONFIG SET命令在线变更,然后通过SENTINEL CONFIG GET核对,而不是手动改动文件。
如果你和我一样,在启动哨兵的路上一路踩坑,别慌,把每条报错和日志都存下来,这套过程本身就是最好的学习材料。之后如果又碰到什么“灵异现象”,我还是会继续往这个主题里补充,记录就是最好的复盘。