尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Redis哨兵机制详解:高可用架构下的自动故障转移实战

Redis哨兵机制详解:高可用架构下的自动故障转移实战 Redis哨兵机制高可用架构的“自动导航系统”我接手过不少Redis生产环境项目发现一个规律很多人对哨兵Sentinel的理解停留在“知道它是做故障转移的”这个层面。真到主节点宕机那一刻业务直接不可写人工切流几百行日志看完才反应过来哪里配置不对。这篇文章不想重复官方文档那一套我想从一个实际运维者的角度把哨兵机制、它解决的核心问题、以及你在部署时一定会踩的坑拆开讲清楚。哨兵机制本质上就是高可用架构里的“自动导航系统”——当Redis主节点失联它自动识别、自动选主、自动切换不需要人半夜爬起来敲命令。整个过程涉及监控、判断、选举、通知四个环节任何一个环节设计不合理都会让“高可用”变成“高危可用”。适合正在做Redis主从架构、准备上生产环境高可用方案、或者面试前想彻底搞懂哨兵原理的读者。1. 为什么Redis需要一套“自动导航系统”——主从模式的痛点1.1 从单点故障说起你没做过生产环境的Redis可能觉得“Redis挂了就挂了重启就行”。但真实业务场景里Redis一旦不可用受影响的往往是所有依赖缓存的接口——用户登录态读不出来、商品详情走不了缓存、限流计数器失效——整个服务链路直接雪崩。我见过一个项目Redis只部署了单节点某天机房交换机异常这台机器失联了20分钟。这20分钟里订单服务所有写操作全部失败数据库直接被击穿CPU打满最终影响了整整一个下午的线上业务。事后复盘结论很简单单点Redis不具备任何故障自愈能力。那加个从节点行不行行但不能解决全部问题。1.2 主从复制解决了部分问题但留下了更大的坑Redis主从复制本身不复杂主节点Master负责读写从节点Slave通过REPLICAOF命令同步主节点数据承担读流量。它的价值在于数据有了冗余读性能有了扩展空间。但主从架构有一个致命的问题——如果主节点挂了呢从节点虽然有一份完整数据备份但它不会自动上位成主节点客户端写请求依然指向旧主节点全部失败必须人工介入选一个从节点执行REPLICAOF NO ONE让它变成主节点再改客户端的连接地址业务才能恢复。这个过程有多痛苦一个典型的接入案例凌晨2点主节点内存异常被系统杀掉值班同学收到告警后先确认主节点确实起不来了然后检查哪个从节点数据最完整依次执行切换命令再更新所有业务客户端的配置并重启应用。前前后后花了40多分钟。这40分钟里Redis写入能力是零。主从复制的另一个隐藏坑是如果没人知道主节点挂了从节点会持续尝试连接主节点并积压复制积压而没有提升机制整个高可用等于空谈。1.3 哨兵机制到底是什么哨兵机制就是来解决上面这个痛点的。它是一组独立运行的守护进程部署在Redis实例之外替你盯着所有主从节点的一举一动。当主节点出现异常它自动完成“选新主、重新指定从节点归属、通知客户端”的完整流程。打个比方主从复制像是车上的备胎——爆胎了有备胎能换但得你自己动手。哨兵机制则是车载导航系统——它不光告诉你爆胎了还自动规划好路线自动带你去最近的修车店甚至帮你把方向盘接管了。把这个“导航系统”拆开看其实就是四个核心职责监控持续检查主节点和从节点是否存活通知节点状态变化时通过API或脚本告知运维人员故障转移主节点失效后自动将一个从节点升级为新主节点配置中心客户端连接时向哨兵询问当前真正的主节点是谁。注意“配置中心”这一点后面第三节实操里还会细讲——很多人在这一步踩了坑。2. 哨兵的工作原理它凭什么敢自动切换主节点2.1 监控发现的第一道判断——主观下线SDOWN哨兵每隔1秒向所有Redis实例发送PING命令。如果某个实例超过down-after-milliseconds参数设置的时间没有任何有效回复哨兵会把这个实例标记为“主观下线”Subjectively Down简称SDOWN。为什么叫“主观”因为这是单个哨兵的判断可能是网络抖动、GC停顿、实例过载导致的短暂无响应不代表节点真的挂掉了。这里有个关键设计思想哨兵判断节点状态不是一次失败就下结论而是要结合连续状态和时间窗口来评估避免误判导致的抖动切换。down-after-milliseconds设置得越短故障发现越快但误判概率也越高设得越长误判越少但故障恢复时间越长。这套逻辑和TCP的超时重传很像——时间窗口的设计就是在快和稳之间找平衡。2.2 客观下线ODOWN与quorum机制一个哨兵说主节点挂了不算数得多个哨兵达成一致。当一个哨兵发现主节点主观下线后它会通过SENTINEL is-master-down-by-addr命令询问其他哨兵对主节点的状态判断。当认为主节点主观下线的哨兵数量达到quorum参数设定的阈值时主节点被判定为“客观下线”Objectively Down简称ODOWN。这个机制其实脱胎于分布式系统的“仲裁”思想。单一观察者永远可能出错只有多数派共识才能作为决策依据。打个比方你看到楼下一个人躺着不动你不会立刻叫救护车你还会让保安、路人也看一眼确认几个人的判断一致了才大概率是真的出事。quorum数值是有讲究的。如果部署了3个哨兵quorum通常设置为2能容忍1个哨兵挂掉如果部署5个哨兵quorum通常设置为3能容忍2个哨兵挂掉。记住一条原则quorum必须小于等于哨兵总数的一半向上取整才有容错空间。如果3个哨兵把quorum设为3一旦有一个哨兵自身宕机就永远无法达成仲裁主节点真挂了也无人处理。2.3 Leader哨兵选举谁有资格执行故障转移就算主节点被确认为客观下线也不是所有哨兵都能立刻动手切换。哨兵之间必须先选举出一个Leader由这个Leader全权负责执行故障转移操作。选举算法借鉴了Raft协议的核心思想。每个哨兵都有资格成为Leader候选人它向其他哨兵发送请求希望获取投票。拿到多数票超过半数的哨兵成为Leader。这里有一个重要细节选举要求候选者必须在线且能与其他哨兵正常通信否则就算票数够了也无法完成后续操作。这个设计解决了“同时多个哨兵都尝试切换主节点”的分布式一致性问题。如果没这个机制两个哨兵同时把不同的从节点提升为主节点整个集群就裂成两半了——这是分布式系统中典型的“脑裂”场景。2.4 故障转移的完整执行流程Leader哨兵胜出后开始真正的切换动作过程可以细分为四步从已下线的旧主节点对应的从节点列表中筛选出健康节点。排除断线中、SDOWN状态、最近通信有异常的从节点。这一步是过滤掉“本身就不靠谱”的候选者按优先级排序选择新主节点。排序规则依次是slave-priority配置数值越小优先级越高、复制偏移量数据越新优先级越高、runid字典序最小者胜出向选中的从节点发送REPLICAOF NO ONE命令让它断开与原主节点的复制关系成为新的主节点向其他从节点发送REPLICAOF new_master_ip new_master_port命令让它们改换门庭复制新主节点。整套切换动作完成后哨兵会更新自己的“主节点地址”记录。这里特别重要客户端连接Redis时不能只连一个直连地址而是应该通过哨兵获取当前主节点地址。否则主节点已经换了客户端还执着地连旧地址照样写入失败。Redis命令里有个对应的客户端侧机制叫做“Sentinel-driven discovery”Jedis、Lettuce等主流客户端都内置支持。配置时指定哨兵地址和master名称客户端会自动感知主节点切换。这是生产环境的正确姿势后面会具体演示。3. 手把手搭建一套生产级哨兵架构3.1 拓扑规划3个哨兵 1主2从我在生产环境中最常用的部署形态是1个主节点、2个从节点、3个哨兵节点。这个拓扑的容错能力是允许1个哨兵节点宕机也允许主节点宕机后有一个从节点接替。具体IP和端口规划如下角色IP端口说明主节点 Master192.168.1.106379读写主节点从节点 Slave-1192.168.1.116379承当读流量从节点 Slave-2192.168.1.126379承当读流量哨兵 Sentinel-1192.168.1.1026379与主节点同机仅演示哨兵 Sentinel-2192.168.1.1126379与从节点同机哨兵 Sentinel-3192.168.1.1226379与从节点同机提示生产环境不建议哨兵和Redis实例完全同机部署。如果一台物理机整体宕机机器上的Redis和哨兵同时不可用部署在同机的哨兵就失去了它应有的独立性。最佳实践是至少有两个哨兵部署在不同机器或不同可用区。上面的拓扑中由于哨兵总数为3即使其中一台物理机全挂剩余2个哨兵仍然满足多数派条件依然能正常工作。3.2 配置主从复制先规划好三台机器的Redis基础配置。主节点和从节点的redis.conf差异只有一个参数——从节点需要配置# 在从节点Slave-1、Slave-2的 redis.conf 里添加 replicaof 192.168.1.10 6379如果Redis版本在5.0之前参数名是slaveof5.0之后统一改成了replicaof。这是Redis刻意去奴隶制术语化的产物很多老教程还在用旧参数名5.0以上版本直接报错。启动三个Redis实例后在主节点执行INFO replication确认复制关系role:master connected_slaves:2 slave0:ip192.168.1.11,port6379,stateonline,offset12345,lag0 slave1:ip192.168.1.12,port6379,stateonline,offset12345,lag0看到两个从节点的state都是online主从复制就没问题了。3.3 配置三个哨兵节点哨兵的配置文件是sentinel.conf。三个哨兵节点配置几乎一样唯一区别是sentinel announce-ip如果跨网段才需要指定。这里给出完整配置# sentinel.conf —— 以 Sentinel-1 为例 port 26379 daemonize yes pidfile /var/run/redis-sentinel.pid logfile /var/log/redis/sentinel.log dir /tmp # 监听监控的主节点 # mymaster 是给这个主节点起的名字客户端配置时需要保持一致 # 最后的 2 就是 quorum表示至少2个哨兵同意才判定客观下线 sentinel monitor mymaster 192.168.1.10 6379 2 # 判定主观下线的超时时间单位毫秒 # 建议设置 10000即10秒无响应才判定为疑似下线 sentinel down-after-milliseconds mymaster 10000 # 故障转移超时时间单位毫秒 # 建议设置 180000即3分钟 sentinel failover-timeout mymaster 180000 # 故障转移时允许多少个从节点同时向新主节点发起全量复制 # 建议设置为 1避免多个从节点同时重同步把新主节点打垮 sentinel parallel-syncs mymaster 1 # 如果Redis设置了密码这里必须配置否则哨兵无法认证 # sentinel auth-pass mymaster 你的密码三个哨兵的配置几乎一致只是每个哨兵上的sentinel monitor都需要指向同一个主节点地址。启动很简单redis-server /etc/redis/sentinel.conf --sentinel启动后通过redis-cli -p 26379 info sentinel可以看到哨兵对主从拓扑的认知# Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_maxlen:128 master0:namemymaster,statusok,address192.168.1.10:6379,slaves2,sentinels3看到sentinels3说明三个哨兵节点已经互相发现了这个架构搭建完成。3.4 客户端与哨兵的对接搭建完成不等于可用客户端连接方式不对切换主节点时客户端一样抓瞎。这里以Java生态最常用的Lettuce和Jedis为例。Jedis的连接池配置方式SetString sentinels new HashSet(); sentinels.add(192.168.1.10:26379); sentinels.add(192.168.1.11:26379); sentinels.add(192.168.1.12:26379); JedisSentinelPool pool new JedisSentinelPool(mymaster, sentinels, redis密码); try (Jedis jedis pool.getResource()) { jedis.set(key, value); }Spring Boot的application.yml配置方式spring: redis: sentinel: master: mymaster nodes: - 192.168.1.10:26379 - 192.168.1.11:26379 - 192.168.1.12:26379关键点在于客户端连接的是哨兵端口26379而不是Redis端口6379。客户端启动时从哨兵拉取当前主节点地址建立真实连接主节点切换后客户端感知到连接断开会重新向哨兵请求新地址。如果客户端配置的是直连6379地址哨兵切换再完美你的应用也没办法自动恢复。注意JedisSentinelPool的master名称必须和sentinel.conf里的sentinel monitor名称一致。很多人在这里踩坑配置里写mymaster哨兵里写my-master结果客户端一直报错“Cannot get master address from sentinel”。4. 生产环境避坑指南——参数调优与故障复盘4.1 down-after-milliseconds到底设多少这个参数很关键直接决定故障恢复速度。设得太小网络抖动就会触发主观下线误判导致不必要的切换设得太大主节点真的挂了业务写入要中断很久。我在生产环境通常设置1000010秒。为什么不是5秒因为Redis主节点偶尔会因RDB持久化子进程写快照或AOF rewrite吃满CPU导致短时间内响应变慢。10秒的窗口能覆盖大部分瞬时卡顿同时又不会让写入中断太久。如果你追求更高的可用性可以缩到5000但必须同时保证操作系统的网络超时参数、TCP keepalive设置合理否则单纯缩哨兵参数只会带来更多无谓故障转移。4.2 quorum和哨兵总数的配合关系这个搭配直接决定你能容忍几个节点挂掉。核心公式很简单哨兵总数Nquorum设为Q则能容忍的故障哨兵数 N - Q。举个例子5个哨兵quorum设为3能容忍2个哨兵挂掉3个哨兵quorum设为2能容忍1个哨兵挂掉。这里有一个很重要的细节如果N是偶数比如4个哨兵quorum设为2看起来能容忍2个哨兵挂掉但在网络分区时如果两边各剩2个哨兵谁也凑不齐3票系统不能自动恢复。所以生产环境里哨兵总数一定取奇数3、5、7这样能尽量避免平票场景。这是一条很容易被忽视但极其重要的架构原则。4.3 我踩过的几个坑坑一脑裂导致的数据丢失。某个项目里主节点192.168.1.10和它的哨兵之间的网络发生了分区哨兵们认为主节点挂了切换到了192.168.1.11。但实际老主节点还活着客户端因为网络原因依然能连上老主节点并写入数据。网络恢复后老主节点被哨兵降级为从节点在新主节点上执行全量同步这期间写入老主节点的数据全部丢失。这个问题的本质不是哨兵的错而是网络分区导致“双主短暂并存”。缓解手段有两个调整min-replicas-to-write 1和min-replicas-max-lag 10参数让主节点在写操作时至少能连上1个从节点且从节点延迟不能超过10秒。如果主节点处于分区状态它无法与任何从节点通信就会拒绝写请求从源头阻断脑裂期间的脏写。从业务层面容忍少量数据丢失。绝大多数缓存场景数据可以从数据库回源重灌没必要做到强一致。坑二failover-timeout设太小导致频繁切换。有一次我把failover-timeout设为3000030秒结果主节点一次长时间GC导致哨兵触发切换后由于网络抖动切换过程在限定时间内没完成哨兵又立刻重试了一次等于切换了两次。从节点数据毫无问题但客户端却经历了两次连接抖动。后来调整到180000这类问题不再出现。原因很简单故障转移涉及选主、通知从节点改复制对象、通知客户端这个流程在物理距离较远时耗时会明显增加预留3分钟才够从容。坑三哨兵配置里遗漏auth-pass。如果Redis开启了密码认证但sentinel.conf里没有配置sentinel auth-pass哨兵能PING通PING命令不受requirepass控制但无法执行INFO repliation、SLAVEOF NO ONE等需要认证的命令导致主节点明明挂了哨兵却拿不到从节点列表故障转移失败。这个问题最隐蔽的地方在于哨兵日志里不会立刻报错要等真正发生故障转移时才发现切换执行不了。坑四三个哨兵部署在同一个Kubernetes节点上。容器化时代容易犯这个错配置文件里写了3个哨兵副本但调度器把三个Pod都调度到了同一台宿主机。宿主机宕机三个哨兵一起挂架构形同虚设。务必在编排里配置PodAntiAffinity强制将哨兵调度到不同节点。坑五主观下线、客观下线与客户端超时的联动。假设你设置down-after-milliseconds10000哨兵从发现异常到完成切换平均需要15秒到30秒。但客户端里如果配置了timeout3000主节点故障后客户端会立刻报错虽然哨兵最终完成了切换但应用侧已经抛出了一堆异常。所以这套时间参数需要整体规划客户端的读写超时时间要小于哨兵判定下线的窗口而哨兵的切换窗口又要小于中间件层的连接超时。生产环境建议把客户端超时设为3秒到5秒哨兵判断窗口设为10秒故障转移总耗时可接受上限设为30秒左右。4.4 故障切换全过程的观察方法哨兵切换过程中日志会依次出现以下关键信息掌握后排查问题会顺利很多# 发现主节点疑似下线 sdown master mymaster 192.168.1.10 6379 # 多个哨兵确认客观下线 odown master mymaster 192.168.1.10 6379 #quorum 2/2 # 选举出Leader哨兵 try-failover master mymaster 192.168.1.10 6379 # 选出了新的主节点 switch-master mymaster 192.168.1.10 6379 192.168.1.11 6379 # 旧主节点恢复后作为从节点接入 convert-to-slave slave 192.168.1.10:6379 192.168.1.10 6379看到switch-master说明切换已经完成。如果一直卡在try-failover阶段优先检查哨兵到从节点的网络以及认证配置。5. 哨兵模式的边界——它和Redis Cluster怎么选5.1 哨兵模式的本质定位哨兵机制的核心价值是高可用不是扩展性。它解决的是“Redis实例不可用时如何快速恢复”的问题但数据容量问题它不负责。一张表看懂哨兵模式和集群模式的区别维度哨兵模式Sentinel集群模式Cluster核心目标高可用自动故障转移高可用 水平扩展数据分片无每个节点存全量数据有数据按槽分配到不同节点单节点数据容量受单机内存限制可扩展到多机客户端复杂度低通过哨兵获取主节点高需处理重定向和槽位自动故障转移由哨兵完成Cluster自身完成适用场景单机内存够用、追求简单数据量大、写流量大、需要分片一个很常见的误区是数据量已经200GB单机内存明显扛不住还在纠结怎么优化哨兵架构。这时应该直接上Cluster配合redis-shake这样的工具平滑迁移而不是继续在哨兵模式下硬扛。5.2 Cluster也存在类似哨兵的机制Redis Cluster本身内置了类似哨兵的高可用能力每个主节点都有对应的从节点当主节点被集群中多数节点报告为失联后从节点会自动发起选举并提升为主节点。它的判断机制依然遵循分布式仲裁思想只是这套机制内嵌在Cluster内部无需再部署独立的哨兵进程。所以如果你的数据容量不需要分片哨兵模式更简洁运维更直观如果单机容量已经吃紧Cluster是从根上解决容量问题的方案。两者不是替代关系而是不同阶段的不同选择。5.3 从哨兵平滑演进到Cluster的经验我实践中比较稳妥的演进路径是先在哨兵模式下通过加内存规格顶住增长等到数据容量确实突破单机瓶颈之前提前做好集群化改造。直接在业务高流量期做热迁移复杂度会陡增。特别提醒使用Pipeline管道批量或者Lua脚本的业务迁移到Cluster前必须先评估key是否涉及跨槽访问。Cluster模式下Pipeline中的多个key必须落在同一个槽内否则会报CROSSSLOT错误。这个改造通常在业务代码层面要提前排期。写在最后的一点体会哨兵机制是个麻雀虽小、五脏俱全的分布式系统里面浓缩了仲裁、选举、状态机、故障转移这些分布式系统核心思想。真学懂它不仅Redis能玩明白对理解其他分布式中间件也大有帮助。根据我的个人经验部署完哨兵架构后千万别觉得就一劳永逸了。基础设施类组件一定要实测故障转移流程。我见过太多团队配置看着一切正常真到主节点宕机时才发现要么哨兵自己先没了要么客户端从不自动重连要么从节点数据堆积严重根本顶不上来。建议每季度做一次故障演练选个低峰期手动停掉主节点Redis进程全程观察哨兵日志和客户端恢复情况确认无异常后再恢复业务。这套机制经得住真故障考验才算真正的高可用。
返回列表