ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Redis哨兵机制全解析:从原理到故障转移实践

Redis哨兵机制全解析:从原理到故障转移实践 最近好多朋友在群里问 Redis 哨兵的问题尤其是线上出了主节点挂掉之后明明配了哨兵客户端还是连不上日志一大把报错。说实话很多人对哨兵机制的理解就停在“能自动切换”这个表面但真到故障发生的时候能不能切得过去、切得多快、中间会不会出幺蛾子这些才是真正要命的点。这篇就把 Redis 哨兵机制完整拆一遍从原理到部署到踩坑一次讲透。哨兵机制是 Redis 保证高可用的核心组件简单说就是用一组独立的哨兵进程帮你看护主从集群主节点挂了自动提拔一个从节点当老大同时通知客户端。它解决的是主从复制模式下“主挂了只能手动切换”的痛点。这篇内容适合正在做 Redis 高可用方案、准备上生产环境或者单纯想彻底弄懂故障转移原理的朋友——不管你是刚学 Redis 的小白还是已经写过几年业务代码的老手看完都能直接落地。1. 为什么需要哨兵机制高可用的第一道防线1.1 从主从复制说起哨兵解决的核心痛点纯主从复制解决了两个问题读写分离和数据备份。主节点负责写从节点负责读从节点同步主节点的数据。但这种模式有一个致命缺陷如果主节点挂掉了从节点虽然有完整数据但它不会自己变成主节点。你只能人工登录服务器手动执行slaveof no one把一个从节点提升为主节点再手动把其他从节点重新指向它。这个过程放在白天业务高峰期至少需要几分钟。几分钟的写入不可用对大多数在线业务来说已经不是事故而是灾难了。更麻烦的是客户端连接的都是主节点的 IP 地址就算你手动切换了客户端的配置还是指向那个已经挂掉的主节点连不上就是连不上。你总不能每个业务服务器都改一遍配置再重启吧。哨兵机制就是为了干掉这两个痛点而生的。它自己跑在一组独立进程里替你盯着所有 Redis 节点一旦发现主节点失联自动完成“选新主、改从属、通知客户端”这一整套动作。整个过程不需要人来干预理想情况下几十秒内就能恢复写入。可以用一个生活化的类比来理解主从复制相当于店里有个备份店长但正店长突然失联时备份店长不会自己决定接店得等总部的电话指示。哨兵机制就是在店门口装了一圈监控配了值班室值班员发现正店长失联立刻让备份店长顶上同时广播告诉大家“以后找新店长”。你说这个值班室有没有必要太有必要了。1.2 哨兵的三大职责拆解官方文档里把哨兵的核心职责总结为三条监控、通知、自动故障转移。我建议再加一条容易被忽略的配置分发。监控哨兵每隔一秒钟向所有主从节点发送一次 PING检测它们是不是还活着。这一秒的间隔是硬编码的不需要配置也基本不用改。通知当主节点发生故障切换后哨兵会通过 Redis 自身的发布订阅机制把新主节点的地址推送给客户端。客户端监听哨兵的频道就能在不用重启的情况下拿到新地址。自动故障转移当确认主节点真的挂了之后哨兵会从众多从节点里挑一个把它升级为新主节点并让其他从节点全部改为复制新主节点。旧的挂掉的主节点等它恢复后会被自动降级为从节点补到新的复制拓扑里。配置分发哨兵还会把当前最新主节点的信息写回哨兵配置文件和内存状态中客户端第一次连接时哪怕没收到推送也可以通过查询哨兵拿到当前主节点是谁。这三加一个职责合在一起就是一套完整的“高可用大脑”。注意哨兵本身是一组进程不是单个进程这样设计的原因在下文讲客观下线的时候会详细解释。2. 哨兵机制的核心原理从心跳到故障转移2.1 心跳检测与主观下线哨兵启动后会与监控的主节点、从节点以及其他哨兵节点建立连接。监控动作的核心是心跳每个哨兵进程每隔 1 秒主动发一条PING命令给所有被监控的节点等待对方回复PONG。如果持续在down-after-milliseconds这个配置项指定的时间范围内没有收到有效回复这个哨兵就会把对应的节点标记为“主观下线”缩写是 SDOWNSubjectively Down。注意“主观”这个词意思是“本哨兵认为它挂了”但其他哨兵不一定这么认为。这里有个关键点要理解down-after-milliseconds不是“判断后立刻切换”的等待时间而是“容忍失联多久才认为可疑”的阈值。比如你配置的是 5000 毫秒那么主节点在 5 秒内没有响应哨兵才开始把它标记为 SDOWN。这 5 秒内的网络抖动、Redis 进程卡顿都不会触发任何动作就是为了避免误伤。主节点被标记为主观下线后哨兵本身也不会立刻做切换因为单个哨兵的判断可能不准确。比如某个哨兵和主节点之间的网络断开了但主节点其实活得好好的其他哨兵都联系得上。那这种情况如果直接切换就会造成两个节点同时认为自己是主节点也就是后面要说的脑裂。2.2 客观下线与投票仲裁单个哨兵发现主节点SDOWN之后会向其他哨兵发起询问通过一条is-master-down-by-addr命令问它们“你们觉得这个主节点挂了吗”。其他哨兵收到后会根据自己的心跳判断结果回复“是”或“否”。当同意主节点已下线的哨兵数量达到配置的quorum值时主节点才会被认定为“客观下线”缩写是 ODOWNObjectively Down。到了这一步才真正触发故障转移流程。这里需要把quorum和“大多数”分清。quorum是一个阈值比如你有 3 个哨兵配置quorum2意思是需要有 2 个哨兵同意下线才判定客观下线。但注意不是超过一半就行而是达到配置数量。如果你只有 2 个哨兵配置quorum1理论上一个哨兵说挂了就算那是默认最小配置也是最低安全性配置。为什么要设置客观下线这个环节为了防脑裂。试想一个集群有 3 个节点、3 个哨兵其中网络分区把一个哨兵和主节点切到了一边另外两个哨兵在另一边。如果只看单个哨兵的判断那 3 个哨兵可能都认为主节点失联了但其实主节点只是和外来网络断开了自己进程完全健康仍然能正常对外服务。这时候所有哨兵都认为挂了并执行切换就会造成两边同时存在“主节点”客户端数据写入就会分叉。有了仲裁机制至少在分布式的多数派还没达成一致之前不会轻易切换。2.3 领导者选举与故障转移流程当主节点被判定为客观下线后哨兵之间会先选出一个“领头哨兵”来主导故障转移。这里用的选举算法和 Raft 类似每个哨兵可以作为候选人或者给其他哨兵投票每个哨兵只有一票候选人在一定时间内获得超过半数的投票就成为领导者。领导者选举完成后真正的故障转移按下面这套流程走选新主节点。领导者会扫描所有健康的从节点过滤掉与主节点断开连接超过一定时间的节点然后按优先级排序首先比较slave-priority配置项值越小优先级越高如果优先级相同就比较复制偏移量谁从主节点同步的数据最新谁上如果还相同再比较 runidrunid 最小的节点被选中。切换身份。对选中的从节点执行SLAVEOF NO ONE让它停止复制关系变成新的主节点。重定向其他从节点。对其余从节点执行SLAVEOF 新主IP 新主端口让它们全部改为复制新主。旧主降级。原主节点会被保留在监控列表中当它重新上线时哨兵会自动对它执行SLAVEOF 新主IP 新主端口让它变成新主的从节点。整个过程看起来一气呵成但要注意其中有个短暂的时间窗口从判定客观下线到新主切换完成期间主节点是不可写入的。这个时间通常取决于哨兵的数量、网络延迟和从节点执行命令的速度配置合理的话一般能控制在一二十秒内。2.4 配置传播与客户端通知故障转移完成之后哨兵并不是就完事了它还有两件重要的事要做更新配置和通知客户端。每个哨兵会通过 Redis 内置的发布订阅频道来广播最新的主节点信息例如switch-master事件。客户端只要事先连接了哨兵就能收到这个事件然后自动把连接对象切换到新主节点。这就是为什么在客户端配置里你写的是哨兵的地址而不是业务主节点的地址。同时哨兵也会把最新的拓扑信息写入自己的配置文件里。比如原来配置的是sentinel monitor mymaster 127.0.0.1 6379 2在一次切换后哨兵会自动把127.0.0.1 6379替换成新主节点的 IP 和端口。这也是为什么哨兵配置文件必须保证有磁盘写权限不能设为只读否则哨兵主从切换后无法持久化状态。3. 哨兵机制部署与配置实操3.1 环境准备安装与基础配置部署哨兵机制前提是先有一套主从集群。Redis 的安装方式很多MAC 上可以用 HomebrewWindows 上也有官方安装包Linux 直接编译安装即可。微信群里很多人问 Windows 怎么装其实现在官方已经有 Windows 版本维护了安装完要把主从和哨兵都配置好。哨兵本质上是 Redis 的一种特殊运行模式由一个独立进程启动默认端口是 26379。最核心的配置文件是sentinel.conf下面是一个生产环境常见的配置模板# 指定哨兵监听端口 port 26379 # 监控的主节点命名为 mymaster # 2 表示 quorum 值即至少需要 2 个哨兵同意主节点下线才判定客观下线 sentinel monitor mymaster 127.0.0.1 6379 2 # 判定主节点失联的最长等待时间毫秒 sentinel down-after-milliseconds mymaster 5000 # 故障转移的超时时间毫秒 sentinel failover-timeout mymaster 10000 # 故障转移过程中最多允许多少个从节点同时与新主节点同步 sentinel parallel-syncs mymaster 1 # 如果主节点配置了密码这里必须配置 # sentinel auth-pass mymaster YourPassword # 保护模式下建议绑定内网 IP或关闭保护模式 # protected-mode no # bind 0.0.0.0每个参数都有讲究单独说一下monitor后面的 IP 和端口是主节点的地址quorum是客观下线的仲裁阈值。我建议生产环境至少用 3 个哨兵quorum 设为 2。这样即使一个哨兵宕机还能凑够票数做判断。down-after-milliseconds配置不要设太短。线上的网络哪怕在同一机房偶尔也会有小抖动设成 3 秒、5 秒比较合理。设成 300 毫秒你可能一个月能触发好几次没必要的切换。parallel-syncs控制切换后在同步数据到新主时候的并发数量。设为 1 是安全的因为从节点同步新主会占用主节点的网络和 CPU 资源并发太多可能把刚上任的新主拖垮。failover-timeout是整套故障转移流程的超时上限。如果设置太短可能在同步和切换过程中出现超时误判。3.2 搭建一个最小高可用集群我以一台机器上跑一主两从三哨兵为例说明部署过程生产环境建议分布在至少三台机器上。实际操作完全可以对照着来。第一步准备三个 Redis 实例。主节点实例6379两个从节点实例6380和6381。从节点的配置文件里要加一行replicaof 127.0.0.1 6379第二步准备三个哨兵实例。分别创建三份sentinel.conf端口分别改成26379、26380、26381每份文件都加上sentinel monitor mymaster 127.0.0.1 6379 2。第三步启动三个 Redis 实例和三个哨兵实例。启动完先看一下主从状态。在主节点上执行redis-cli -p 6379 info replication正常情况下会看到role:master下面有两个从节点。再在任意一个哨兵上执行redis-cli -p 26379 sentinel master mymaster可以看到哨兵视角下的主节点信息包括地址、端口、被监控的从节点数量。第四步模拟主节点故障。直接kill掉主节点进程然后盯着哨兵日志看。正常情况下大约 5 秒后会看到类似下面的事件sdown master mymaster 127.0.0.1 6379 odown master mymaster 127.0.0.1 6379 #quorum 2/2 switch-master mymaster 127.0.0.1 6379 127.0.0.1 6380switch-master出现就说明切换成功了。这时候再连原来的从节点 6380执行info replication会发现它已经变成主节点了而且 6381 已经自动成了它的从节点。接下来的重要一步是把旧主节点重启。等它重新上线后你会发现它已经不再是主节点而是自动降级为从节点复制新主节点的数据。这个自动降级行为是哨兵完成的不需要你手动配置。第三步和第四步中间的关键点是客户端那边怎么接入。如果你在客户端里配置的是6379端口那切换后百分之百连不上。正确做法是客户端通过哨兵来发现主节点。3.3 客户端侧接入配置以常见的 Java 生态为例Spring Boot 的 Redis 配置天然支持哨兵模式spring: redis: host: 127.0.0.1 port: 6379 sentinel: master: mymaster nodes: 127.0.0.1:26379,127.0.0.1:26380,127.0.0.1:26381注意这里虽然写了host和port但哨兵模式生效时客户端优先使用哨兵列表去获取当前主节点地址而不是直接用host:port。在 Go 的go-redis里也是这样提供了FailoverClient或者SentinelClient配置类型直接传入MasterName和SentinelAddrs。这里有个容易被坑的点客户端连接哨兵获取主节点地址时必须保证客户端所在网络环境能够访问哨兵端口和主节点端口。不要只在本地能连线上实例的网络策略没放通结果哨兵切换成功了但客户端还是连不上新主然后你排查半天以为是哨兵的问题。4. 常见问题、坑与排查技巧实录4.1 脑裂问题为什么哨兵会有“假多主”脑裂是哨兵机制里最容易被忽略、但后果最严重的问题。当一个主节点与外界网络隔离但进程本身还活着时如果哨兵因为收不到心跳把它判定为客观下线并且大多数哨兵都在网络分区外那么分区外的哨兵就会选出一个新主而分区内的主节点还在继续接收写入请求。这就造成了一个典型的“双主”现象。网络分区恢复后旧主会被降级为从节点但它作为“主节点”期间接受到的写入数据会因为复制关系重建而丢失而且无法找回。这是异步复制模式下所有高可用方案都要面对的取舍哨兵并没有办法完全解决。缓解手段有三个哨兵数量不要少于 3 个并且 quorum 不要设成 1。多个哨兵投票能有效减少误判。在从节点上开启min-replicas-to-write和min-replicas-max-lag参数限制主节点在失去足够从节点反馈时的写入能力。比如设置min-replicas-to-write 1意思是一个从节点都没有正常同步时主节点直接拒绝写入这样即使发生脑裂也只有极少数据能写进旧主。在应用层尽量使用幂等写入或做数据补偿这属于高可用架构设计层面的兜底。4.2 配置与权限问题哨兵部署里有一类特别常见的坑几乎每个新手都会踩一次主节点配了密码但哨兵没配认证信息。结果哨兵连不上主节点心跳检测一直失败和主节点相关的所有监控全部失灵。解决办法很直接在sentinel.conf里加上sentinel auth-pass mymaster YourPassword还要注意哨兵之间如果需要互相通信、互相投票也得保证网络可达。如果开了protected-mode那么外部机器的哨兵和客户端将无法访问该哨兵。建议在安全的内网环境里把哨兵节点的bind和protected-mode配置成允许内网访问但不要直接暴露到公网。另外不要把所有哨兵部署在同一台物理机上。我之前见过有人为了图省事一主两从三哨兵全部跑在一台 8G 内存的虚拟机里。机器一挂所有哨兵和节点全没了高可用直接变成了高可吹。至少要把三个哨兵分散到不同机器或不同可用区才能真正起到仲裁作用。4.3 常见故障排查速查表我把线上维护过程中比较典型的问题整理成一张速查表方便遇到的时候直接对照着查。故障现象可能原因排查方法主节点挂了客户端长时间连不上新主客户端用的是普通连接没走哨兵发现检查客户端连接配置确认是否配置了哨兵地址主节点没有发生切换日志显示 sdown 后又恢复网络瞬时抖动未达到 down-after 阈值查看 sentinel 日志是否出现sdown/-sdown适当调大阈值切换完成后新主很快又挂新主资源不足或从节点并发同步压力过大调低parallel-syncs检查新主机器的 CPU、内存哨兵配置文件被自动修改哨兵在切换后自动更新配置属正常现象确认配置文件有写权限不要让 sentinel 只读启动发下switch-master但客户端拿到旧地址客户端缓存了旧连接没有监听哨兵通知查看客户端文档确认是否启用了哨兵订阅更新机制哨兵日志频繁出现投票/选举哨兵数量过少或网络不稳定增加哨兵节点检查哨兵之间的网络质量4.4 个人经验与优化心得最后分享几个我实际维护哨兵集群攒下来的习惯希望能帮你少踩几个坑。第一个是关于down-after-milliseconds的调参。我见过很多项目把它设成 1000 毫秒结果上线的第一周就莫名其妙触发了三四次自动切换每次切换都有一个短暂只读窗口虽然是半夜没造成大事故但吓人。后来统一调成 5000 到 10000 毫秒基本就没再发生误切。判断依据很简单线上 Redis 的主节点很少因为进程内部故障而不可用倒是因为网络抖动、磁盘打满、内存 swap 这些外部因素失联所以判断阈值要给足缓冲。第二个是故障演练一定要做。部署完哨兵集群后不要直接上线了事。我习惯在灰度环境里故意杀一次主节点进程盯哨兵日志确认switch-master事件正常打出再把旧主重启看它能否自动降级。这个动作成本极低但能帮你发现配置里的低级错误比如密码不对、哨兵端口没放通、quorum 配错等等。第三个是监控建议。哨兵自身也是进程也可能挂掉。如果所有哨兵同时挂掉主节点出问题时照样没有高可用兜底。所以线上应该用独立的监控系统比如 Prometheus Alertmanager对哨兵进程、哨兵端口、sentinel master输出做周期性探测。一旦发现哨兵失联立即告警而不是等事故真的发生了才去找原因。最后再补充一个细节日志里的switch-master就是你判断切换是否成功的唯一金标准。遇到任何“到底切没切成功”的疑问第一件事就是去翻哨兵日志不要猜。日志里有完整的时间线从sdown到odown到switch-master每一步都写得很清楚。学哨兵机制最大的收获不是记住那一堆配置项而是理解故障转移的完整时间线。把这个时间线吃透了线上无论出什么问题你都能快速定位到具体环节。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进