ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Redis哨兵机制详解:从主从复制到自动化故障转移实战

Redis哨兵机制详解:从主从复制到自动化故障转移实战 聊到Redis的高可用哨兵Sentinel是一个绕不开的话题。很多人把主从复制搭起来就觉得万事大吉实际上主库一旦宕机从库虽然还在但不会自动顶上业务写入直接失败最后还是得人工介入——手动把某个从库提升为主库再让其他从库重新指向它。整个过程慢则几分钟快则几十秒中间业务一直处于不可写状态。哨兵就是来解决这个问题的它专门盯着你的Redis主从集群一旦发现主节点故障自动完成故障转移顺带把客户端也引到新的主库上。这套机制同时还承担通知和服务发现的功能算是Redis生态里做高可用的主力方案。这篇文章我会把哨兵的核心机制拆开来讲包括主观下线SDOWN、客观下线ODOWN到底怎么判定故障转移的完整流程是怎样的配置文件里每个关键参数应该怎么理解再附上我实际部署和排查过程中总结的一些经验和坑。不管你是后端开发还是运维只要想让Redis具备自动故障恢复能力这篇文章应该能给你一个清晰完整的落地参考。1. 哨兵机制整体设计与核心思路拆解1.1 先搞清楚哨兵到底在解决什么问题先理一下最基础的演进逻辑。单机Redis最大的问题是单点故障机器宕机、内存损坏、网络隔离任何一个意外都可能导致整个缓存服务不可用。后来我们有了主从复制一个主库负责写多个从库负责读主库数据同步到从库这解决了读压力和部分容灾问题——比如主库挂了从库上的数据还在可以抢救。但问题也在这里主库挂了以后**谁来接管写请求**从库默认是只读的不会自动变成主库。在没有哨兵的日子里这个“切换主库”的动作通常靠人肉完成先检查主库是不是真的挂了再挑一个数据最完整的从库手动执行SLAVEOF NO ONE让它变成主库然后让其他从库重新指向它最后还得改客户端的连接配置重启应用。一套流程下来运气好三五分钟运气不好就是一次小型事故。哨兵做的事情就是把上面这一套人工判断和执行流程全部程序化。它像一个监督员24小时盯着主库和从库的状态一旦判定主库故障自动执行主从切换整个过程不需要人工干预业务方甚至感知不到切换发生。这就是哨兵最核心的价值让Redis从“半自动容灾”变成“自动高可用”。1.2 三个核心职能的落地逻辑哨兵的职责可以拆成三块这也是官方文档里明确的定位。第一监控Monitoring。哨兵会定时向主库、从库以及其他哨兵发送PING命令通过响应情况判断节点是否存活。这个监控不是一次性的而是周期性持续进行每个哨兵都在独立做这件事信息汇总后形成集体判断。你可以理解为每个哨兵都是一个“目击证人”大家各自描述自己看到的情况最后按规则综合得出结论。第二自动故障转移Automatic Failover。如果多个哨兵达成一致认为主库确实不可用了就会进入故障转移流程推选出一个leader哨兵由它负责挑选一个从库提升为新主库再让其他从库重新指向新主库最后通知客户端切换连接地址。整个过程最关键的是“自动”二字从我观察到的实际切换时间来看配置合理的情况下通常在几秒到十几秒内就能完成具体取决于down-after-milliseconds和判定流程的时间。第三通知与服务发现Notification and Service Discovery。哨兵的另一个身份是“地址服务”。主库发生切换后原来的主库地址就变了或者说角色变了客户端如果还连接着旧地址写入肯定失败。哨兵提供了一种机制客户端可以主动向哨兵发起询问获取当前真正的master地址。同时哨兵在节点状态发生变化时还会通过pub/sub发布事件消息其他系统可以订阅这些消息来做感知和联动。这里很多人会忽略一个点**哨兵本身也需要“高可用”。**如果只部署一个哨兵它自己挂了整个监控体系就失效了。所以生产环境通常部署三个或更多哨兵哨兵与哨兵之间也互相监控并通过投票机制协商决策。这样即使某个哨兵节点故障整体依然能正常工作。这也是后面理解SDOWN和ODOWN的关键前提——单个哨兵说了不算要多数派达成共识才算数。2. 核心机制深度解析SDOWN与ODOWN2.1 主观下线SDOWN单个哨兵的独立判断先记住一个原则**每条判定“下线”的消息都不是最终结论而是需要一个逐步确认的过程。**主观下线SDOWNSubjectively Down就是这个过程中的第一步。每个哨兵会按照配置的间隔默认1秒向被监控的Redis节点发送PING命令。如果在一个down-after-milliseconds周期内没有收到该节点的有效响应——这里的有效指PONG、LOADING或MASTERDOWN等可以接受的回复——哨兵就会将这个节点标记为“主观下线”。为什么叫“主观”因为这是单个哨兵自己的判断。它只代表“我联系不到这个节点了”并不代表“这个节点真的挂了”。你自己想想如果你和一个朋友约好每10秒通一次电话结果连续60秒没打通你大概率会认为他出事了。但在实际场景里你俩所在的区域可能正在经历信号屏蔽他其实一切正常。哨兵的主观下线也是同理可能主库本身没问题只是哨兵和它之间的网络出现了抖动或分区。所以主观下线只是一个信号不是判决。它存在的意义是**当一个节点连一个哨兵都联系不上的时候至少值得怀疑和求证。**这个时候哨兵会通过pub/sub信道向其他哨兵发出问询看看别人是否也联系不上主库。2.2 客观下线ODOWN多个哨兵达成共识客观下线ODOWNObjectively Down是多哨兵协商后的最终结论。流程大致是一个哨兵对主库标记了SDOWN之后会向其他哨兵发送SENTINEL is-master-down-by-addr这类命令询问它们对同一主库的看法。如果在指定时间内收到足够数量也就是quorum参数定义的数量的哨兵回复“这个主库也不可达”那么该主库就被确认为客观下线只有到了这个阶段才允许进入故障转移流程。这里要重点理解quorum参数的语义。它代表的是“达成共识所需的最小票数”而不是“哨兵总数”。比如配置了3个哨兵quorum设为2意思是至少2个哨兵都认为主库有问题才能触发切换。为什么需要这个阈值核心是避免网络分区造成的误判。设想一下机房A部署了主库和两个哨兵机房B部署了一个从库和一个哨兵两台机房之间的专线断了。机房B的哨兵会发现自己联系不上主库如果它单方面就能触发故障转移就会把机房B的从库提升为主库。但此时机房A的主库其实一直在正常运行客户端也正常读写。网络恢复后旧主库发现有了新主库会把自己降级为从库然后做全量同步期间数据一致性会出现大问题。有了quorum机制机房B只有一个哨兵必然凑不够票数从而避免了误切换。quorum设多少合适一般是“多数派”即quorum 哨兵总数 / 2 1。3个哨兵时就是25个哨兵时就是3。这样既保证能达成共识又能容忍部分哨兵节点故障。2.3 判定流程中的细节与避坑**down-after-milliseconds不宜设置过短。**我见过有人为了追求快速切换把这个参数设成2000ms甚至1000ms结果网络稍微一抖动主库就被标记下线紧接着触发切换。切换本身是有代价的旧主库如果恢复会变成从库并重新同步全量数据期间还可能丢失一部分未同步的写入。我踩过这个坑当时线上交换设备做了一次几秒内的闪断结果半小时内主库被切换了三次业务侧写入报错频繁最后排查日志才发现是判定阈值太敏感。经验值内网环境建议从10000ms起步云环境或跨机房环境建议30000ms以上再配合实际网络状况做调整。**quorum不能等于哨兵总数。**如果3个哨兵把quorum设为3好处是判定非常严格必须所有人都觉得主库挂了才切换。坏处是只要有一个哨兵进程异常退出或者网络隔离票数永远凑不满故障转移机制就瘫痪了。**自动切换追求的不是“绝对不误判”而是“在可接受的误判率下保证可用性”。**多数派是最合理的平衡点。**SDOWN对从库和哨兵同样适用。**很多人以为哨兵只监控主库其实从库和哨兵节点也在监控范围内。从库的主观下线不会触发故障转移但会影响主库选举时对候选节点的筛选哨兵之间的主观下线则会影响投票、选主等交互过程设计架构时要把这些因素都考虑进去。3. 自动故障转移与选主机制3.1 完整故障转移流程拆解一个主库被判定为客观下线后进入故障转移阶段。这个阶段看似只是“选个新主库”实际背后是一套非常严谨的流程我按执行顺序拆开讲。**第一步选举leader哨兵。**故障转移需要一个“执行者”。参与判定客观下线的哨兵节点之间会进行一次投票选出一个leader负责实施后续操作。这个过程基于Raft共识算法思路实现目的是避免多个哨兵同时执行切换造成状态混乱。可以理解为团队里出了事故大家先推举一个组长出来牵头处理而不是所有人一拥而上。**第二步从从库中筛选新主库候选人。**leader哨兵会遍历所有健康状态下的从库淘汰掉不达标的与主库连接断开时间过长、复制延迟过大的从库会被排除。剩下的从中按规则选出一个最合适的。这一步是保证数据尽可能完整的关键。**第三步晋升新主库。**leader哨兵向选中的从库发送SLAVEOF NO ONE命令让它停止复制并变成新的主库同时将该实例的旧主身份清除。这个操作执行完新主库就可以开始接收写请求了。**第四步让其他从库重新指向新主库。**leader哨兵向剩余从库发送SLAVEOF new-master-ip new-master-port命令让它们开始从新主库复制数据。这里有一个并行度的控制避免多个从库同时做全量同步把新主库拖垮。**第五步通知客户端并更新配置。**哨兵会更新自身的monitor配置把主库的地址换成新主库。如果旧主库后续恢复上线哨兵会发现它是一个“曾经的master”会把它降级为从库并指向新主库保证整个集群收敛到一个正确状态。3.2 新主库怎么选优先级、偏移量、runid很多人会误以为“随便挑一个从库当主库”其实选举是有明确排序规则的依次比较三个维度顺序维度含义规则1replica-priority从库的优先级配置数字越小优先级越高0表示永不参与选举2复制偏移量从库已复制的数据位置偏移量越大数据越完整优先选3Run ID实例的唯一标识按字典序排序最小的胜出这三个维度的优先级是递减的先比优先级分不出胜负再比偏移量还持平才比Run ID。replica-priority是一个容易被忽略的参数。如果某台从库所在机器硬件较差或者承担了较重的工作负载可以手动把这个值调大让它“知难而退”。如果某些从库上面跑着定时任务、数据分析脚本这类重负载程序把它replica-priority设为0明确告诉哨兵“别选我当主库”能减少切换后的潜在风险。3.3 脑裂与数据丢失主从切换最被诟病的场景哨兵机制有一个先天性的痛点**网络分区期间的数据丢失。**假设主库A和两个哨兵在同一个网络分区内旧主库B在另一个分区但客户端还在向B写入。哨兵们验证B联系不上判定B客观下线并提升了从库A为新主库。但与此同时B还在接收写入只是这些数据无法同步给任何从库——因为B连自己的从库都联系不上而它作为“主库”仍然可以对外提供服务。等网络恢复B发现集群里出现了新主库它会自动降级为从库然后同步新主库的数据自己分区期间接收的那部分写入就会被丢弃。这是分布式系统里典型的脑裂问题本质上不是哨兵的bug而是CAP的取舍——保证可用性就可能牺牲一致性。Redis无法完全消除这个问题但提供一个缓解手段min-replicas-to-write 1 min-replicas-max-lag 10这两个配置作用于主库如果主库在min-replicas-max-lag秒内没有至少min-replicas-to-write个从库正常同步就会拒绝写入。这样在网络分区时旧主库B发现从库都联系不上写入直接被拒绝等网络恢复后丢失的数据量就被控制住了甚至可以达到零丢失。代价是什么可用性下降。正常情况下如果从库因故障掉线主库也可能触发拒绝写入。是否启用、阈值设多少需要结合业务对数据损失的容忍度来权衡。我自己的线上实践是核心交易类数据宁可拒绝写入也不允许丢失就从源头开启了这个配置日志、排行榜类缓存数据则关闭。4. 配置文件与实操部署4.1 一套最小可用的哨兵架构理论讲得再多最终还是要落到部署上。我推荐的最小生产架构是一主两从三哨兵。主从三个节点负责数据读写三个哨兵节点独立于Redis实例部署互相监控并协商决策。为什么不是一主一从一哨兵因为quorum至少需要2一个哨兵永远凑不够多数派故障转移机制形同虚设。为什么要三个哨兵而不是两个两个哨兵在其中一个故障时也只能凑到1票同样无法达成共识。三个哨兵在允许一个节点故障的情况下依然能满足quorum2。以Docker部署为例先创建一个自定义网络docker network create redis-ha然后启动主从三个Redis实例。主库docker run -d --name redis-master \ --network redis-ha \ -p 6379:6379 \ -v /data/redis-master:/data \ redis:7.0 redis-server \ --appendonly yes \ --requirepass YourPass123两个从库在redis-server后面追加--replicaof参数指向主库即可docker run -d --name redis-replica-1 \ --network redis-ha \ -p 6380:6379 \ -v /data/redis-replica-1:/data \ redis:7.0 redis-server \ --appendonly yes \ --requirepass YourPass123 \ --replicaof redis-master 6379第二个从库同理端口换成6381名字换成redis-replica-2。这里有几个细节要注意容器内Redis端口默认都是6379replicaof指向的地址应该用容器名redis-master而不是127.0.0.1开启了requirepass的情况下从库还需要配置masterauth才能通过认证完成复制常用的做法是通过redis-cli命令动态修改或者启动时追加参数。4.2 sentinel.conf 的关键配置项哨兵本身也是Redis实例只不过跑的是redis-sentinel模式。配置文件的路径和启动方式因部署形态而异但核心配置项是一致的。我先贴一个最小可用的sentinel.confport 26379 daemonize yes logfile /var/log/redis/sentinel.log pidfile /var/run/redis-sentinel.pid sentinel monitor mymaster redis-master 6379 2 sentinel auth-pass mymaster YourPass123 sentinel down-after-milliseconds mymaster 10000 sentinel failover-timeout mymaster 60000 sentinel parallel-syncs mymaster 1逐项拆解这五个核心配置sentinel monitor mymaster redis-master 6379 2这一行定义了监控目标。mymaster是逻辑主库名同一套哨兵集群里的名字必须完全一致后面是主库地址和端口最后的2就是quorum。这里有个非常隐蔽的坑如果主库地址写的是127.0.0.1而哨兵跑在另一个容器或机器上那哨兵永远只能监控到本机的Redis压根连不到真正的主库。部署时要根据实际网络拓扑填写可路由的地址。sentinel auth-pass mymaster YourPass123主库开启了密码认证哨兵和主库通信也需要密码。主库和所有从库的密码必须一致否则后期切换时哨兵可能无法和晋升的新主库建立连接。sentinel down-after-milliseconds mymaster 10000这就是之前反复提到的判定阈值。单位毫秒设为10000表示哨兵在10秒内连续联系不上主库就标记为主观下线。这个值控制着故障转移的触发速度设太大切换慢设太小容易误判。sentinel failover-timeout mymaster 60000故障转移的超时时间。如果leader哨兵在60秒内没有完成整个切换流程这次故障转移会被判定为失败后续可能重新触发。这算是一个保守措施防止切换卡死在某一步导致系统长时间不可用。sentinel parallel-syncs mymaster 1切换完成后允许同时有几个从库对新主库做全量同步。设为1意味着逐个同步避免多个从库同时全量复制把刚晋升的新主库压垮。如果从库数量少、数据量大建议保持1如果从库多且数据量小可以适当调高加快收敛速度。4.3 验证故障转移三个关键测试配置完成后启动三个哨兵在哨兵机器上执行redis-cli -p 26379 sentinel masters应该能看到主库处于ok状态。接下来做一次实战演练。测试一杀主库。直接停掉主库容器docker stop redis-master然后观察哨兵日志。正常情况下你会在日志里看到这样几条关键记录我整理成了简化的流程说明标记 redis-master 为主观下线 向其他哨兵发送问询收集判定意见 收到的客观下线票数达到 quorum标记 redis-master 为客观下线 发起故障转移流程选举leader哨兵 选择一个候选从库执行 SLAVEOF NO ONE 通知其他从库同步新主库信息 更新配置发布切换完成事件测试二查新主库。故障转移完成后通过任意哨兵查询当前主库地址redis-cli -p 26379 sentinel get-master-addr-by-name mymaster如果返回的是之前某一个从库的地址和端口说明切换成功。测试三旧主恢复。重启刚才停止的旧主库容器。注意观察它的角色变化哨兵会发现它是一个曾经的master会主动让它成为新主库的从库。最终集群恢复成“一主两从”的状态只是主库角色已经换人。这个验证流程建议在每次调整哨兵配置后都跑一遍不要等线上真的出了故障才第一次见证切换那太刺激了。5. 常见问题与排查技巧实录5.1 故障转移常见问题速查表实际运维中哨兵集群的问题往往不是配置写错而是各种边缘场景叠加。我整理了一张排查表都是真实遇到过的案例。现象可能原因排查思路日志显示sdown但一直不进入odownquorum设置过大票数凑不满检查quorum配置确认哨兵节点间网络互通触发切换后迟迟不完成failover-timeout过大或leader哨兵异常查看哨兵日志中的投票阶段确认是否有哨兵断联客户端切换后仍连不上主库客户端没有使用哨兵发现机制地址硬编码客户端配置spring.redis.sentinel.master和节点列表新主库数据明显滞后选举时未优先考虑复制偏移量检查从库的复制状态确认延迟是否过高旧主库恢复后一直做同步但数据对不上脑裂期间产生了丢失写入评估是否开启min-replicas-to-write必要时人工修复数据哨兵之间无法通信bind、防火墙、安全组限制确认哨兵端口互访权限哨兵也需要互相监控5.2 部署和运维中的几个独门经验先说说哨兵节点的部署位置。**不要把哨兵和数据节点放在同一台物理机或同一个容器里。**我见过一个案例某公司把主库和哨兵部署在同一台虚拟机虚拟机宕机时Redis挂了哨兵也跟着挂了剩下的哨兵凑不够quorum自动切换完全没有触发。最后恢复还是靠人工。哨兵存在的意义是“从外部观察集群”如果它和数据节点同生共死那就失去了监控的意义。再说配置同步问题。哨兵运行时可能会修改自己的配置文件——例如主库切换后sentinel monitor mymaster那行会更新为新的主库地址。所以在容器化部署时文件系统必须是可写的同时不要手动去改一个正在运行的哨兵配置文件它可能在下次状态变更时覆盖掉你的修改。正确做法是修改配置后重启哨兵进程。还有一个很容易踩的细节**所有哨兵配置文件里的mymaster名称必须完全一致。**如果哨兵A配置的是mymaster哨兵B配置的是master1这俩虽然监控的是同一个Redis实例但它们之间无法形成一致的判断和投票故障转移时会出现各说各话的诡异状态。排查这类问题多花几分钟核对配置文件比瞎猜效率高得多。最后一点是关于parallel-syncs的调优。当从库数量很多时比如5个以上如果parallel-syncs设为1集群收敛到健康状态的时间会明显拉长因为从库在排队等待全量同步。可以改为2或3但要先确认新主库的网络带宽和磁盘IO扛得住并发同步。我一般建议小规格实例从1开始观察切换后的主库负载再逐步调整。6. 哨兵与Cluster集群选哪个6.1 两者的核心定位差异很多初学Redis的人会混淆哨兵和Cluster以为都是高可用方案选一个就行。其实两者的定位完全不同。哨兵解决的是“主从结构下的自动故障转移”它管的是高可用不管数据分片Cluster解决的是“数据量大到单机放不下时的水平扩展”它自带分片功能也内置了类似哨兵的故障检测和切换机制。做一个直白的对比维度哨兵模式Cluster集群数据存储所有数据在主库从库复制全量数据按slot分散到多个主节点容量扩展依赖单机内存大小增加节点即可水平扩容高可用哨兵负责监控和自动切换集群内部节点互相监控并切换客户端逻辑通过哨兵获取主库地址通过cluster协议计算slot路由运维复杂度相对简单节点间通信、slot迁移、数据重分配更复杂6.2 选型建议数据量才是最关键的判断标准我个人的选型逻辑很简单预估数据总量是否超过单机可用内存。如果未来一两年的数据量增长靠一台高配机器就能轻松扛住那就优先用哨兵模式简单、直观、排查问题也容易。很多中小规模业务核心缓存数据控制在2~4GB以内单机16GB内存绰绰有余上Cluster完全没必要。如果数据量已经到几十GB甚至上百GB单机内存无法承载这时必须考虑Cluster。Cluster把数据分成16384个slot分布在不同节点上每个节点只承担一部分数据既解决了容量问题也天然具备了故障转移能力。代价是客户端逻辑更复杂运维时需要关注slot迁移、集群状态一致性等问题。还有一类折中方案在实际业务中也常看到早期先用哨兵模式数据规模增长后迁移到Cluster。迁移过程可以借助工具灰度推进但建议架构上留好后路——从一开始在客户端代码中就抽象出“获取主库地址”的服务层无论是哨兵的地址发现还是Cluster的路由计算都封装在内部后续切换不至于改动业务代码。回到哨兵本身我想说一个很多人忽略的点哨兵这套机制不是Redis的“附属功能”它在设计上就是独立于数据节点运作的。理解了这个思想你才会明白为什么官方反复强调哨兵要部署奇数个、quorum要设为多数派、监控对象不能只看主库。它不是可有可无的旁路组件而是Redis高可用体系中的核心决策层。把SDOWN、ODOWN、选主、脑裂补偿这一整套逻辑吃透才能真正在故障来临时不慌。
返回列表