ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Redis哨兵机制详解:原理、故障转移与实战避坑

Redis哨兵机制详解:原理、故障转移与实战避坑 Redis相关的高可用话题聊到哨兵机制的时候很多人的理解就停在“挂了会自动切换”这个层面。但只记住这句话面试官只要追问一句“怎么避免误判”“切换期间写请求会怎样”基本就露馅了。这篇文章是面经系列的第五篇专门把哨兵机制从原理、配置、故障转移到面试追问一次讲透也适合正在折腾Redis高可用方案的开发、运维直接照着搭。内容里会夹杂一些我自己部署和切换演练时踩过的坑尽量把文档里不会写的细节也翻出来。1. 主从复制解决了什么又留下了什么隐患1.1 没有哨兵时主节点宕机的真实困境先回到最基础的问题为什么要做主从复制主从复制解决的是单点故障、读写分离和冷备问题。一个主节点带几个从节点读请求可以分散到从节点上主节点专注于写从节点还能作为物理备份就算主节点数据盘坏了从节点也能顶上。这是很多人已经掌握的部分。但主从复制本身不能自动完成故障转移。主节点一旦宕机系统就进入一个很尴尬的状态写请求全部失败因为只有主节点能接收写从节点虽然存活但只能提供读而且读到的还是旧数据。更麻烦的是这时候需要人工介入确认主节点是真的挂了还是只是临时抖动。从一堆从节点里挑一个数据最新的执行REPLICAOF NO ONE让它变成新主。修改所有客户端连接配置把写地址从旧主切到新主。让剩下所有从节点执行REPLICAOF 新主IP 端口重新建立复制关系。旧主恢复之后还要把它降级成新主的从节点避免出现两个主节点同时写数据。整个过程哪怕再熟练也至少需要几分钟。大多数真实事故发生在凌晨值班人员要在告警、查日志、翻文档、敲命令之间来回折腾等到应用恢复业务已经挂了很久。所以主从复制只是高可用的“地基”真正让系统自动恢复的是哨兵机制。1.2 哨兵机制解决的三个核心问题哨兵Sentinel本质上是一组独立运行的进程它不存储业务数据只负责盯着Redis节点。它主要解决三个问题监控哨兵会周期性向主节点、从节点发送PING命令确认它们是否存活。这个监控是持续性的不是人肉巡检。自动故障转移当主节点被判断为故障时哨兵会从从节点中选举出一个新主节点并自动把其他从节点指向新主。整个过程不需要人工参与。配置通知与客户端发现哨兵保存了当前主节点的地址客户端可以向哨兵询问“现在的主是谁”。主节点切换后哨兵会通过发布订阅频道通知客户端客户端就能自动连到新主上。用生活化的话说主从复制是“备胎”哨兵是那个随时能判断正主是不是挂了、然后立刻把备胎扶正的调度员。没有调度员备胎就永远只是备胎。需要注意的是哨兵解决的是“高可用”不是“数据不丢失”。故障转移过程中旧主上还没同步到从节点的写数据在切换后就是丢了。这个问题后面会专门展开。2. 哨兵集群的组建与心跳感知细节2.1 为什么不推荐只部署一个哨兵新手最容易犯的错误就是只启动一个哨兵进程。部署一个哨兵当然也能监控主节点但它有两个致命问题第一哨兵自身成为新的单点。哨兵挂了整个高可用机制就跟着失效等于回到了没有哨兵的状态。第二单个哨兵对主节点状态的判断可能不准确。如果某个哨兵所在机器发生网络抖动或者主节点因为一次长GC、一次慢查询导致几秒内没有响应哨兵就可能误判主节点已死进而触发切换。但实际上主节点还活着这就会造成不必要的故障转移甚至引发脑裂。所以生产环境至少部署3个哨兵而且建议部署在不同物理机或者不同机架上。为啥是3个因为哨兵的故障转移和客观下线判断依赖“多数派”3个哨兵可以容忍挂掉1个仍然能凑出2个的多数5个哨兵能容忍挂掉2个。如果只部署2个其中1个挂了之后剩下1个哨兵无法形成多数派整个高可用会陷入瘫痪。这不是拍脑袋定的要求而是分布式系统里经典的“2N1”容错模型要容忍N个节点故障至少需要2N1个节点。2.2 哨兵如何发现彼此、如何发现所有节点刚接触哨兵时我特别困惑我只在配置里写了主节点地址哨兵是怎么知道有哪些从节点、有哪些其他哨兵的其实靠的是自动发现。每个哨兵启动时配置里至少有这一行sentinel monitor mymaster 192.168.1.10 6379 2这一行告诉哨兵你要监控的主节点叫mymaster地址是192.168.1.10:6379至少需要2个哨兵同意才能判定它客观下线。有了主节点地址哨兵会向主节点发起连接然后周期性执行INFO命令。主节点的INFO输出里会包含所有从节点的IP和端口信息哨兵据此建立对从节点的认识并开始监控这些从节点。那哨兵之间怎么互相发现呢哨兵连接主节点时会同时建立一个发布订阅连接订阅一个特殊频道__sentinel__:hello。每个哨兵会定期把自己的信息运行ID、IP、端口、配置纪元等发布到这个频道由于所有哨兵都连着同一个主节点相当于都进了同一个“群聊”在这个频道里就能发现彼此。之后哨兵之间会直接建立连接不再依赖主节点中转。这里有个容易被忽略的细节哨兵和Redis节点之间既要有命令连接又要有发布订阅连接。命令连接用来发PING、INFO、SLAVEOF等指令发布订阅连接专门用来接收频道消息。如果只建立命令连接哨兵无法及时感知其他哨兵的配置更新可能导致切换时对主节点认知不一致。2.3 主观下线与客观下线的判定逻辑哨兵对节点的故障判断分成两个阶段主观下线Subjectively Down简称SDOWN和客观下线Objectively Down简称ODOWN。哨兵每隔1秒向被监控节点发送PING如果在down-after-milliseconds配置的时间内没有收到有效回复这个哨兵就会把该节点标记为主观下线。什么叫有效回复PONG、LOADING、MASTERDOWN这些正常响应都算连接超时、回复错误甚至只收包不回复都不算。注意这个超时是针对单个哨兵自己的判断所以叫“主观”。但主观下线只代表“我觉得它挂了”可能是我这半边网络出问题实际主节点活得好好的。为了避免单个哨兵误判触发切换只有当主节点被标记为主观下线后该哨兵才会发起询问它通过SENTINEL is-master-down-by-addr命令向其他哨兵广播“我认为主节点挂了你们怎么看”其他哨兵会根据自己的探测结果回复“同意”或“不同意”。当同意数量达到配置里的quorum值主节点才会被标记为客观下线。比如3个哨兵、quorum2就是至少2个哨兵都认为主节点不可用才执行后续故障转移。从节点如果被标记为主观下线不会触发客观下线流程。因为从节点挂了不影响写服务只需要在选举新主时把它排除掉就行。这也是容易搞混的地方面试时经常有人答成“哨兵会对所有节点做客观下线”这是不严谨的。3. 故障转移的完整执行链路3.1 领导者哨兵的选举机制主节点被判定为客观下线之后并不是每个哨兵都跑去执行切换。如果大家都动手会出现多个哨兵同时给不同从节点发SLAVEOF命令结果整个集群状态乱成一锅粥。所以哨兵之间要先选出一个“领导者”由它全权负责这次故障转移。这个选举机制和Raft算法的思路很像每个哨兵都可以发起选举目标是让自己成为领导者。具体过程大致是哨兵向其他哨兵发送“选我当领导者”的请求其他哨兵在同一个配置纪元configuration epoch内只能投一票。哪个哨兵先获得超过半数哨兵的同意它就当选。每个哨兵都有一份配置纪元你可以把它理解成“版本号”。每次发生主节点切换纪元都会加1用于标记这次切换产生的配置是新的。其他哨兵收到新的配置后如果发现纪元更大就会更新自己的配置。这样就算有旧的哨兵消息延迟到达也不会覆盖新配置。这里面试官常会追问如果选举没有产生领导者怎么办哨兵不会死循环它会等待一个随机时间后再次发起选举。这有点类似于解决缓存击穿里“加随机过期时间”的思路降低多个哨兵同时发起选举的冲突概率。实际生产环境哨兵数量少、网络正常的情况下一般一轮选举就能选出领导者。3.2 新主节点的筛选依据领导者哨兵选出后开始从所有从节点里挑“新主子”这个挑选过程有三个主要依据优先级slave-priorityRedis 5.0之后叫replica-priority。这个值越小优先级越高。比如我们希望某台配置更高、网络更好的机器当主节点就可以把它的优先级设为1其他机器设成2。复制偏移量如果优先级相同就比复制偏移量。复制偏移量表示从节点已经从主节点同步到了第几个字节偏移量越大说明数据越接近主节点选它当主丢的数据越少。运行ID如果优先级和偏移量都一样就选运行ID字典序最小的那个。这个条件基本是兜底保证一定有一个确定的结果。除了这三个排序条件还会做一些过滤连接断开的从节点不选最近回复过哨兵PING的从节点才考虑长时间没有跟主节点完成同步的从节点也不选。如果所有从节点都不满足条件这次故障转移会失败哨兵会等下一个周期再重新挑选。从节点数量太少也不行。主节点挂了但没有任何一个健康的从节点那哨兵能做的只是不断重试。所以生产环境至少给每个主节点配1到2个从节点而且要确保从节点的复制链路健康。3.3 从节点重新指向新主节点选定新主之后领导者哨兵会执行以下操作向新主节点发送REPLICAOF NO ONE让它脱离从节点身份变成真正的主节点。向其他所有从节点发送REPLICAOF 新主IP 新主端口让它们立刻开始复制新主的数据。如果旧主节点后来恢复了哨兵检测到旧主重新在线会执行REPLICAOF 新主IP 新主端口让旧主降级成新主的从节点。这里有个很反直觉的点旧主恢复后它不会保有原来的主身份而是被哨兵自动改成从节点。如果旧主在宕机期间又接收过写请求脑裂场景这些数据会随着重新同步被新主的数据覆盖相当于这部分数据永久丢失。所以千万不要以为哨兵能做数据找回。还有一点要注意向新主节点发送REPLICAOF NO ONE之后要等新主节点确认自己已经切换成功哨兵才会去通知其他从节点。如果第一步就失败整个切换流程中止等下个周期重新尝试。所以故障转移并不是瞬间完成的它有一个执行窗口。3.4 通知客户端和后续处理故障转移最后一步是让所有人知道“主节点换了”。哨兵会把最新的主节点地址写入自己的配置然后通过发布订阅频道对外发出事件通知最典型的是switch-master事件。客户端要享受这种“自动切换”必须使用支持哨兵模式的客户端。比如Jedis可以这么初始化SetString sentinels new HashSet(); sentinels.add(192.168.1.20:26379); sentinels.add(192.168.1.21:26379); sentinels.add(192.168.1.22:26379); JedisSentinelPool pool new JedisSentinelPool(mymaster, sentinels);Lettuce也有类似的RedisSentinelClient。这些客户端会先去哨兵那里询问当前主节点地址然后建立连接当主节点切换发生时客户端通过订阅哨兵频道感知到变化自动重建连接。如果客户端不支持哨兵模式那就只能手动改配置重启应用高可用就大打折扣。所以选型时要确认客户端库是否原生支持Sentinel。4. 关键配置项逐条拆解与部署建议4.1 sentinel.conf 核心配置哨兵的配置文件看起来不长但每一行都值得抠。下面是我常用的配置模板port 26379 daemonize no protected-mode no logfile /var/log/redis/sentinel.log sentinel monitor mymaster 192.168.1.10 6379 2 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 15000 sentinel parallel-syncs mymaster 1 sentinel auth-pass mymaster Redis2024 sentinel resolve-hostnames no sentinel announce-ip 192.168.1.20 sentinel announce-port 26379逐条拆开讲sentinel monitor mymaster 192.168.1.10 6379 2监控的主节点名称、地址和端口最后的2就是quorum值。sentinel down-after-milliseconds mymaster 5000如果5秒内没有收到有效PING回复就主观下线。这个值不能拍脑袋写太短容易误判太长会拉长故障恢复时间。sentinel failover-timeout mymaster 15000故障转移超时时间。如果切换过程中某个步骤超时哨兵会重新尝试。这个值也影响从节点重新配置的超时判断。sentinel parallel-syncs mymaster 1切换完成后同时允许几个从节点向新主发起全量同步。一般建议1避免多个从节点同时全量同步把新主和网络压垮。sentinel auth-pass mymaster Redis2024如果Redis开启密码认证这里必须配置否则哨兵连日志里的INFO都拉不回来。sentinel resolve-hostnames no默认不解析主机名直接用IP。如果你的环境用DNS动态解析需要按需开启。sentinel announce-ip/sentinel announce-port容器或NAT环境下哨兵对外宣布的IP端口需要显式配置。这是容器部署时最容易踩的坑后面单独讲。4.2 quorum和哨兵数量怎么定quorum是哨兵配置文件里最需要想清楚的参数。它代表“至少几个哨兵同意才能客观下线主节点”。但这个值并不是越大越好。先说哨兵数量。推荐3个或5个必须保证能凑出多数派。3个哨兵能容忍1个挂掉5个能容忍2个挂掉。如果只有2个哨兵即使把quorum设成2一旦其中一个哨兵挂掉另一个哨兵无论如何也无法获得多数票主节点真挂了也不会切换。quorum建议设置为“超过哨兵总数的一半”。3个哨兵就设25个哨兵就设3。这样既保证能形成客观下线共识又保留了容错空间。如果3个哨兵把quorum设成3就意味着3个哨兵必须都认为主节点挂了才能切换万一某台哨兵网络抖了一下主节点真故障时也无法完成客观下线等于高可用失效。所以生产上常见组合是3个哨兵 quorum2或5个哨兵 quorum3。宁可让误判概率低一些也不要为了极端情况牺牲可用性。4.3 用Docker Compose搭建主从加哨兵很多人喜欢用Docker快速搭一套环境来实验这确实是成本最低的方式。但在容器里跑哨兵有个必须处理的难题容器内部IP和外部网络IP不一致哨兵默认公布的是容器IP外部客户端根本连不上。举一个Docker Compose的简化配置三个Redis节点加三个哨兵services: redis-master: image: redis:7.2 command: [redis-server, --requirepass, Redis2024, --appendonly, yes] redis-slave1: image: redis:7.2 command: [redis-server, --replicaof, redis-master, 6379, --masterauth, Redis2024, --requirepass, Redis2024] redis-slave2: image: redis:7.2 command: [redis-server, --replicaof, redis-master, 6379, --masterauth, Redis2024, --requirepass, Redis2024] sentinel1: image: redis:7.2 command: [redis-sentinel, /etc/redis/sentinel.conf] volumes: - ./sentinel1.conf:/etc/redis/sentinel.conf关键点在于每个哨兵配置文件里都要写清楚sentinel monitor mymaster redis-master 6379 2 sentinel announce-ip 127.0.0.1 sentinel announce-port 26380redis-master这个主机名在容器网络内能解析但客户端在外面解析不到。所以当哨兵通过SENTINEL get-master-addr-by-name mymaster返回地址时不能让客户端去连redis-master而需要设置announce-ip为宿主机可达的IP。端口也有讲究如果容器和宿主机做了端口映射announce-port要填映射后的宿主机端口。这种坑特别隐蔽因为哨兵自己看起来一切正常info sentinel里也能看到主从节点信息但客户端怎么连都连不上。我建议无论是否用容器部署都要在自己环境里验证一遍从客户端视角拿到的地址是否真的可达。5. 面试官最爱追问的进阶问题与典型场景5.1 主节点脑裂与数据丢失问题脑裂是哨兵模式最经典的面试题也是生产环境最容易出问题的地方。场景是这样的主节点和哨兵集群之间的网络被切断了但主节点本身还活着依然接收客户端的写请求。哨兵联系不上主节点于是判定它客观下线选举从节点提升为新主。网络分区恢复后旧主发现自己已经变成了“别人眼中的从”这时哨兵会让它同步新主的数据。但旧主在分区期间接收的那些写请求根本没有机会同步给新主于是全部丢失。如果业务不能接受这个窗口期的数据丢失哨兵模式就不够用。Redis官方提供了两个缓解参数min-replicas-to-write 1 min-replicas-max-lag 10第一个参数表示主节点至少要有1个从节点连接正常才接受写请求第二个参数表示从节点最大复制延迟不能超过10秒。一旦从节点数量不够或复制延迟太大主节点会直接拒绝写请求。这能在脑裂发生时收缩写入口减少数据丢失窗口但没法完全消除。面试时能说出“用这两个参数限制脑裂窗口”会显得有实操经验。另外哨兵模式也不等于数据安全。如果Redis只开了RDB默认配置故障转移可能丢失最近的写数据。建议生产环境开启AOF或者至少用appendfsync everysec并且把RDB和AOF都放到独立磁盘上。这样就算切换数据丢失范围也能尽量缩小。5.2 哨兵切换期间客户端会经历什么很多人以为哨兵切换是“无缝”的其实不是。从主节点故障到新主对外提供服务中间有一段时间窗口。这个窗口由三部分构成主观下线耗时取决于down-after-milliseconds比如配置5秒。客观下线确认耗时哨兵向其他哨兵询问并统计票数网络正常时这步很快毫秒到百毫秒级。故障转移执行耗时选举领导者、筛选从节点、执行REPLICAOF NO ONE、通知其他从节点一般也是秒级。所以整体切换耗时通常是5到15秒。在窗口期内如果客户端请求打到旧主节点会直接连接失败或超时如果哨兵已经把故障转移结果发布出去客户端订阅到switch-master事件后会重建连接。也就是说高可用系统的“可用性”不等于“无感知”应用层必须要有重试机制。这也是为什么我建议客户端侧配置连接池和超时时间时要留出足够余量。有些框架默认的请求超时只有1秒主节点切换期间所有请求都会失败业务就报错了。可以把重试次数调到3次配合退避策略等切换完成后再访问。5.3 哨兵模式下Redis分布式锁还可靠吗每个做Redis分布式锁的人都会遇到这个问题主节点挂了锁数据是不是也跟着丢了答案是有可能。比如客户端A在主节点上拿到了锁还没执行完业务主节点宕机。哨兵切换到从节点时如果从节点还没有同步到这把锁的数据那么新主节点上“锁不存在”。客户端B这时去获取同一把锁也能成功两个客户端同时进入临界区分布式锁就失效了。所以哨兵可以保证Redis服务高可用但不能保证分布式锁的强一致性。如果有业务不能接受这个风险有几种思路使用Redlock算法向多个独立Redis节点加锁只要超过半数成功就算获锁。但这个方案一直有争议很多分布式系统专家认为它在一些极端场景下仍有问题。对锁的可靠性要求极高的场景直接使用ZooKeeper或etcd这类强一致协调服务。在业务侧做兜底比如数据库唯一索引、版本号、分布式事务ID这些手段比单纯依赖Redis锁更可靠。面试时被问到Redis分布式锁最好主动把哨兵模式的这个短板抛出来再讲自己的选型思路比死记硬背Redlock流程要强。5.4 哨兵模式与Redis Cluster怎么选这也是面试必问的选型题。哨兵模式解决的是单点故障和自动切换问题但整个集群仍然只有一个主节点写能力受限于单机。Redis Cluster则采用数据分片把数据分散到多个主节点每个主节点又有自己的从节点故障转移粒度更小。用一张表对比对比项哨兵 主从Redis Cluster数据分片无有16384个槽位写扩展不支持单主写入支持多主水平扩展自动故障转移哨兵负责Cluster内部处理客户端要求支持哨兵模式的客户端即可支持Cluster协议的客户端数据量上限受单机内存限制可横向扩展配置复杂度相对低相对高如果单机内存够用、写QPS不高、读多写少哨兵主从是最稳妥的方案如果数据量已经超过单机内存或者写QPS持续上涨就应该考虑Cluster。有些公司用哨兵模式把业务扛到几十GB乃至上百GB硬扛到最后还是拆库拆表反而更痛苦。前期就把数据规模预估清楚比后期迁移更现实。6. 实战踩坑记录与监控运营建议6.1 我在切换过程中踩过的坑第一个坑是容器部署的announce-ip问题。有一次我在测试环境用Docker搭了主从和哨兵哨兵日志显示一切正常info sentinel也能看到主从节点列表但用JumpServer跳板机上的客户端连接时一直报连接超时。后来用redis-cli -p 26379 sentinel get-master-addr-by-name mymaster一查返回的是容器内部IP172.17.0.x外部客户端根本访问不到。解决办法就是在每个哨兵配置文件里加上sentinel announce-ip和sentinel announce-port指向外部能访问的地址。第二个坑是密码配置。我给Redis设置了requirepass但忘了在哨兵配置里加sentinel auth-pass mymaster 密码。结果哨兵启动后日志里一直刷-NOAUTH Authentication requiredinfo sentinel显示的从节点数量是0。原因就是哨兵连上主节点后需要执行INFO来发现从节点但没有认证权限自然拿不到从节点列表。更隐蔽的是主从复制之间的密码也要配masterauth否则从节点无法向主节点同步。这套密码三个地方都要一致Redis实例的requirepass、从节点的masterauth、哨兵的auth-pass。第三个坑是切换后从节点不同步。有个环境的主从架构是“1主2从”故障转移后新主起来了但另一个从节点的复制一直起不来。查了复制状态发现是它在原主节点上用的认证信息还是旧密码切换到新主后不匹配。后来统一改造了账号密码管理把所有节点的masterauth都改成同一个值这个问题才彻底消失。6.2 配置与监控的最佳实践首先哨兵日志里这几个关键字必须重点关注sdown表示主观下线odown表示客观下线switch-master表示主节点切换完成failover-end表示故障转移流程结束。建议把哨兵日志接入日志采集系统遇到这些事件立刻告警。其次可以定时用redis-cli -p 26379 info sentinel拉取哨兵状态重点关注sentinel_masters、sentinel_ok、master0:slaves这些指标。如果哨兵数量、从节点数量变化都要能主动发现。可视化工具方面Redis Desktop Manager或Another Redis Desktop Manager主要用来看Redis实例状态看哨兵信息还是命令行更直接。再强调一个容易被忽略的点哨兵不应该和Redis主节点部署在同一台物理机。如果一台物理机宕机很可能Redis主节点和部署在上面的哨兵一起挂。极端情况下这台物理机上的哨兵还会发送“主节点客观下线”的投票其他哨兵根据票数可能误切换。正确做法是哨兵错开机器部署至少保证半数的哨兵不跟Redis主节点同机。6.3 切换演练怎么做才不翻车我见过很多团队部署完哨兵就再也没碰过直到线上故障才第一次看到切换日志结果各种意外。哨兵机制一定要通过演练验证演练流程可以这样设计准备阶段选择业务低峰期通知相关方备份当前主节点数据记录切换前各节点复制偏移量。模拟主节点故障不建议直接kill -9主进程因为进程被杀后连接会立即断开和真实网络分区表现不完全一样。可以用debug sleep 30让主线程阻塞30秒这样客户端和哨兵都会感知到主节点“无响应”更贴近网络分区。观察阶段一边看哨兵日志一边记录客户端报错数量确认切换耗时和告警是否如预期触发。验证阶段确认新主节点是谁数据是否完整所有从节点是否同步到新主旧主恢复后是否自动降级为新主的从节点。恢复阶段如果需要切回原主可以手动执行REPLICAOF NO ONE让原主重新上位或者直接让原主保持从节点身份即可。大多数生产环境不需要刻意切回让新主长期担任主节点也没问题。演练至少要连续跑3次每次都要记录切换耗时。如果某次切换耗时明显变长可能就是哨兵配置或网络环境有问题。我经历过的最疼的一次就是在没有演练的情况下直接上线结果切换后从节点全连不上新主排查了半天才发现是masterauth没配置。踏踏实实把演练做扎实再去面试或者谈高可用方案心里都踏实得多。
返回列表