ARTICLE DETAIL

资讯详情

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

Redis哨兵机制详解:从主从复制到自动故障转移的完整实践

Redis哨兵机制详解:从主从复制到自动故障转移的完整实践 Redis 做高可用绕不开哨兵。这机制说简单也简单主库挂了从库顶上客户端无感知继续读写。但真到生产环境细节远比想象的多。我见过不少团队Redis 主从复制配好了觉得万事大吉结果主节点一宕整个业务链路直接卡死因为没人处理故障转移。也有人费劲搭了哨兵却因为配置参数理解不到位出现了脑裂、误判、切换失败等更头疼的问题。这篇面经我把哨兵机制从设计思路到落地实操再到问题排查完整拆开来讲希望能帮你少踩几个我踩过的坑。这篇内容适合谁准备 Redis 面试的开发者正在设计高可用架构的技术负责人以及那些已经用了 Redis 但还没上哨兵、或者上了哨兵但心里没底的朋友。看完你至少能搞明白三件事哨兵到底在守护什么、核心参数怎么调才合理、以及故障切换时背后到底发生了什么。我尽量用大白话讲但涉及关键配置和流程的地方会给出精确的命令和参数方便你直接抄作业。1. 哨兵机制整体设计先搞懂它解决什么问题1.1 为什么主从复制不够用Redis 主从复制解决的是读压力和数据备份问题。主库负责写从库负责读数据实时同步。听起来很完美但这里有个致命缺陷主库挂了怎么办从库虽然有完整数据但它不会自己上位。你只能手动执行SLAVEOF NO ONE把一个从库提升为主库再通知所有客户端修改连接地址。这个过程如果发生在凌晨三点负责的人可能睡得很香业务就长时间不可用。更麻烦的是客户端连接的是具体 IP 和端口。主库切换后地址变了所有客户端都得改配置重启这在高并发场景下简直就是灾难。哨兵机制存在的意义就是把这套手动流程自动化监控主库状态发现异常时自动完成从库晋升、客户端重定向。它本质上是一个独立运行的高可用守护进程不参与业务读写只负责看护。1.2 哨兵集群一个哨兵不够至少三个起步单个哨兵存在单点问题。如果哨兵自己挂了整个高可用体系就失效了。所以生产环境中的哨兵通常是奇数个节点组成一个哨兵集群。为什么要奇数这和客观下线的判定机制有关后面会详细说这里先记住结论哨兵数量建议至少 3 个且不要跟 Redis 主从节点完全部署在同一台机器上。哨兵集群之间通过 Redis 的发布订阅机制互相通信。每个哨兵会周期性向其他哨兵发送心跳同时监听其他哨兵发布的频道消息。它们还会通过INFO命令获取主从节点的拓扑信息包括当前主库地址、从库列表、复制偏移量等。这些信息是故障转移决策的基础。我见过一个很典型的部署架构3 个哨兵节点分布在 3 台不同机器上监控一对主从节点。两个 Redis 数据节点跑在一台物理机的不同容器里哨兵则放在独立的服务器上。这样即使 Redis 所在机器挂掉哨兵还能存活能正常发起故障转移。2. 核心概念与关键参数这些细节必须吃透2.1 主观下线与客观下线哨兵怎么判定主库挂了这是哨兵机制里最容易混淆的概念也是面试高频考点。主观下线指的是单个哨兵从自己的视角判断某个节点不可用。每个哨兵会周期性向监控的节点发送PING命令如果在down-after-milliseconds指定的时间内没有收到有效回复这个哨兵就认为该节点主观下线了。主观下线只是单方面判断有可能是网络抖动导致的误判。为了降低误判影响哨兵引入客观下线机制当某个哨兵认为主库主观下线后会向其他哨兵发起询问询问它们眼中的主库状态。如果收到足够数量的确认数量由quorum参数控制才能认定主库真的挂了进入故障转移流程。这里有个关键点quorum不是投票同意的意思而是确认主观下线的哨兵数量阈值。比如有 5 个哨兵quorum设为 3意味着至少有 3 个哨兵认为主库下线才能触发客观下线。但实际选举 leader 哨兵来执行故障转移时需要大部分哨兵同意即超过总数的一半。2.2 故障转移流程从发现问题到主库切换客观下线确认后哨兵集群会选出一个 leader 哨兵来执行故障转移。选举机制用的是 Raft 算法每个哨兵都有投票权获得超过半数的票数才能成为 leader。成为 leader 后故障转移分三步走第一步从所有从库中筛选出候选从库。过滤规则是断线时间超过一定阈值的从库直接排除优先级低的靠后。这里slave-priority参数很关键你可以手动指定某个从库优先级最高比如配置更好的机器。第二步从候选从库中选出最终胜出者。选主逻辑按照优先级从高到低、复制偏移量从大到小、运行 ID 字典序最小三个条件依次比较。优先级相同就看谁的数据最完整复制偏移量最大数据都一致就按运行 ID 排序。第三步执行切换。leader 哨兵向选中的从库发送SLAVEOF NO ONE命令让它停止复制并提升为主库。然后向其他从库发送SLAVEOF 新主库IP 新主库端口让它们重新指向新主库。最后更新内部记录等旧主库恢复后它会变成新主库的从库。2.3 脑裂问题为什么怎么配置都可能出现双主脑裂是 Redis 哨兵机制最让人头疼的问题。场景是这样的主库和哨兵之间的网络被切断但主库和客户端之间依然是通的。哨兵判定主库主观下线触发客观下线并完成故障转移选举出新的主库。此时旧主库还在正常运行还在接收客户端写入就出现了双主现象。最典型的后果是数据丢失。新主库是原来的从库它只恢复到之前同步到的位置而旧主库在断网期间写入的数据全部丢失。等网络恢复旧主库降级为从库这些数据因为与主库不一致会被丢弃。应对方案有两个层面。架构层面用min-replicas-to-write和min-replicas-max-lag参数控制主库写入策略如果从库数量少于指定值或者从库同步延迟超过指定秒数主库直接拒绝写入。这样即使发生脑裂旧主库在无人同步的情况下也会停止写入最大化减少数据丢失。业务层面应该在客户端启用本地缓存或降级策略避免极端情况下的数据不可用。关于这两个配置参数的组合调优我先卖个关子后面在常见问题排查部分会展开说一版我踩坑后总结的参数组合。3. 实操部署从零搭建一套哨兵架构3.1 环境准备与节点规划我这次用 Docker 来演示方便快速搭建和清理环境。准备一台 Linux 服务器安装好 Docker 和 docker-compose。规划如下角色节点名称端口主库redis-master6379从库1redis-replica-16380从库2redis-replica-26381哨兵1sentinel-126379哨兵2sentinel-226380哨兵3sentinel-326381注意这里哨兵没有使用默认的 26379 统一端口而是各自独立端口是为了在同一台机器上演示多个哨兵节点。生产环境建议哨兵端口统一分布在不同的物理机上。3.2 主从节点配置与启动先编写主库配置redis-master.confport 6379 bind 0.0.0.0 protected-mode no daemonize no appendonly yes appendfsync everysec从库配置只需要在此基础上添加一行replicaof 127.0.0.1 6379然后用 docker-compose 一键启动version: 3 services: redis-master: image: redis:7.0 container_name: redis-master ports: - 6379:6379 volumes: - ./conf/redis-master.conf:/etc/redis/redis.conf command: [redis-server, /etc/redis/redis.conf] redis-replica-1: image: redis:7.0 container_name: redis-replica-1 ports: - 6380:6379 volumes: - ./conf/redis-replica-1.conf:/etc/redis/redis.conf command: [redis-server, /etc/redis/redis.conf] redis-replica-2: image: redis:7.0 container_name: redis-replica-2 ports: - 6381:6379 volumes: - ./conf/redis-replica-2.conf:/etc/redis/redis.conf command: [redis-server, /etc/redis/redis.conf]启动后验证主从状态docker exec redis-master redis-cli INFO replication看到role:master且connected_slaves:2说明主从搭建成功。再执行docker exec redis-replica-1 redis-cli INFO replication应该看到role:slave和master_link_status:up。3.3 三个哨兵节点配置与启动哨兵配置sentinel.conf核心内容如下port 26379 bind 0.0.0.0 protected-mode no 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 monitor后面跟着的是监控名称、主库 IP、主库端口和 quorum。这里 quorum 为 2表示至少 2 个哨兵确认主库下线才能触发故障转移。down-after-milliseconds是主观下线判定阈值设置为 5000 毫秒即 5 秒内主库没有响应就标记为下线。这个值不宜太小否则网络抖动容易误判也不宜太大否则故障转移太慢。failover-timeout是故障转移超时时间包括所有从库完成切换的时间。设置太小会导致切换未完成就被判定失败设置太大会拖长整体不可用时间。parallel-syncs表示故障转移后同时向新主库发起复制的从库数量。设置为 1 表示逐个同步避免多个从库同时全量复制压垮新主库。另外两个哨兵节点的配置完全一样只需要修改端口号。哨兵之间会自动发现彼此并建立监控关系。启动哨兵docker run -d --name sentinel-1 -p 26379:26379 \ -v ./conf/sentinel-1.conf:/etc/redis/sentinel.conf \ redis:7.0 redis-sentinel /etc/redis/sentinel.conf启动后查看哨兵日志会输出类似monitor master mymaster 127.0.0.1 6379的日志以及已经发现从库的信息。3.4 压测演练手动杀主库观察自动切换环境搭建完成后做一次故障演练验证效果。先向主库写入一些测试数据docker exec redis-master redis-cli SET foo bar docker exec redis-master redis-cli SET counter 100然后手动停掉主库容器模拟宕机docker stop redis-master观察哨兵日志docker logs -f sentinel-1日志输出逻辑大致如下sdown master mymaster 127.0.0.1 6379 odown master mymaster 127.0.0.1 6379 #quorum 2/2 try-failover master mymaster 127.0.0.1 6379 vote-for-leader elected-leader master mymaster 127.0.0.1 6379 failover-state-select-slave selected-slave slave 127.0.0.1:6381 failover-state-send-slaveof-noone failover-state-wait-promotion promoted-slave slave 127.0.0.1:6381 failover-state-reconf-slaves slave-reconf-sent slave-reconf-inprog failover-end当看到promoted-slave和failover-end时说明故障转移完成。此时新主库变成了原来的 6381 端口上的从库。验证数据docker exec redis-replica-2 redis-cli GET foo如果返回bar说明数据完整切换成功。再查看原从库 6380 的复制状态应该看到master_host已经指向新的主库地址。整个切换过程消耗时间大约在 5-15 秒之间具体取决于down-after-milliseconds和failover-timeout的配置值。这里有个体验优化点后续会单独说。4. 常见问题与排查技巧实录4.1 哨兵误判与频繁切换问题问题现象主库运行正常但哨兵频繁触发切换业务间歇性感知到连接中断。原因分析最常见的原因是down-after-milliseconds设置过小比如设置成 1000 毫秒。Redis 在极端情况下可能出现短暂阻塞比如持久化策略设置为appendfsync always加上磁盘 IO 抖动导致主库在 1 秒内没有响应哨兵的 PING 请求。哨兵误判为下线触发切换。解决方案调整down-after-milliseconds到 5000-10000 毫秒。这个值本质上是故障感知的灵敏度需要结合业务容忍度和网络稳定性来权衡。我在实际项目中内部网络环境稳定设置为 5000 毫秒比较合适跨机房场景建议设置 10000 毫秒以上。另一个隐藏因素哨兵通过PING命令检测节点健康状态如果 Redis 节点上配置了rename-command PING或rename-command INFO会导致哨兵监控功能异常出现误判。尽量避免对内部监控命令做重命名。4.2 故障转移后数据丢失问题问题现象主库宕机哨兵完成切换后部分最近写入的数据丢失。原因分析Redis 主从复制是异步的。主库接收到写请求返回给客户端成功但底层可能还没同步到从库。此时主库死了从库晋升为主库后就会缺失这部分数据。无论哨兵机制多可靠这个数据窗口都不可避免。使用WAIT命令可以同步等待但会付出性能代价通常不推荐每个写操作都等。解决方案结合min-replicas-to-write和min-replicas-max-lag参数控制风险。我推荐一组配置min-replicas-to-write 1min-replicas-max-lag 10。含义是如果从库数量小于 1 个或者从库最大延迟超过 10 秒主库拒绝写入。这样做的目的是保证至少有一个从库同步相对及时降低数据丢失窗口。注意这组参数是写在主库配置里的与哨兵配置无关。补充说明一下min-replicas-to-write结合min-replicas-max-lag的完整优化经验。很多资料只提前者不强调后者实际上两者需要配合。因为前者只检查从库是否存在但没检查延迟一个从库断线 5 分钟它依然存在但数据已严重滞后。再加上min-replicas-max-lag后主库才会真的在从库复制跟不上的时候暂停对外写入。我用这套组合方案在多个项目里验证过既保证了可用性又显著缩小了极端故障下的数据丢失窗口。极简结论只要追求极致性能Redis 主从架构必然存在极端情况下的秒级数据丢失风险。如果业务无法容忍需要引入更复杂的方案比如全同步写机制牺牲性能或者业务层做补偿。4.3 旧主库恢复后首轮同步报错问题问题现象旧主库机器恢复后哨兵将其降级为从库但日志大量报错MASTER aborted replication。原因分析旧主库恢复时新主库已经有大量新数据。旧主库作为从库进行全量同步如果数据量很大首轮全量同步可能需要较长时间。期间新主库持续接收新写入复制偏移量不断增大如果旧主库的全量同步时间超过了client-output-buffer-limit的限制连接会被强制断开。解决方案在主库配置里调大从库输出缓冲区限制。推荐参数组合client-output-buffer-limit replica 512mb 128mb 60意思是给从库连接分配的最大缓冲区为 512MB如果连续 60 秒超过 128MB则断开连接。生产环境建议根据数据大小适当调整这个值。另外尽量在业务低峰期恢复旧主库节点减缓全量同步压力。4.4 客户端连接哨兵的常见误区问题现象客户端配置了哨兵地址但主库切换后仍然连不上。原因分析大部分客户端连接哨兵的方式不是直连主库而是通过哨兵获取当前主库地址。如果你使用的客户端框架没有正确实现 Sentinel 模式就会出问题。以 Jedis 为例正确配置方式SetString sentinels new HashSet(); sentinels.add(127.0.0.1:26379); sentinels.add(127.0.0.1:26380); sentinels.add(127.0.0.1:26381); JedisSentinelPool pool new JedisSentinelPool(mymaster, sentinels);关键点mymaster必须和哨兵配置里的sentinel monitor mymaster ...名称一致。我遇到过有人配置名称不一致客户端始终找不到可用主库。对于 Spring Boot 项目配置文件这样写spring: redis: sentinel: master: mymaster nodes: - 127.0.0.1:26379 - 127.0.0.1:26380 - 127.0.0.1:26381注意 Spring Boot 的旧版本有 bug在哨兵模式下不会动态感知主库地址变化需要升级到 2.1.0 以上版本才能较好地支持故障转移后的自动重连。4.5 面试实战哨兵机制高频问题速答这部分是我整理面试题时经常被问到的几个点回答思路给到你们问Redis 哨兵机制如何保证高可用答哨兵通过三种能力保障高可用监控、通知、自动故障转移。监控是指哨兵周期性检测主从节点的存活状态通知是指哨兵通过发布订阅在集群内部传递状态变化自动故障转移是指当主库客观下线时哨兵集群选举 leader筛选出最优从库并提升为新主库同时更新其他从库的复制指向。这整个过程对客户端透明客户端只需要记住哨兵地址即可。问为什么哨兵至少需要 3 个节点答哨兵选举 leader 和判断客观下线都需要超过半数的节点同意。如果有 2 个哨兵一台挂掉后剩余 1 个无法组成大多数故障转移无法执行。3 个节点允许挂 1 个5 个节点允许挂 2 个以此类推。问如何避免哨兵误判答调大down-after-milliseconds参数同时确保监控网络环境稳定。另外部署哨兵时不要全部放在同一台机器上避免单点物理故障引发所有哨兵同时失联。问哨兵模式下分布式锁是否一定安全答不安全。分布式锁依赖的是 Redis 的SET NX PX指令在哨兵模式下如果主库获取锁后同步到从库前发生切换锁信息会丢失。业务上能接受的场景包括秒杀、任务调度等非强一致场景但涉及资金、订单等核心数据建议使用 RedLock 方案或者引入其他分布式协调组件。我在实际面试中聊到分布式锁时发现很多人会把哨兵模式和分布式锁的安全性混为一谈。这里要再强调一遍哨兵解决的是高可用不是一致性。锁的安全性和高可用是两套体系不能互相替代。最后分享两个实战小技巧第一哨兵配置里有个容易被忽略的坑如果 Redis 设置了密码哨兵配置文件里也必须同步设置。否则哨兵无法正常通过INFO命令获取主从节点的拓扑信息会报NOAUTH Authentication required错误。配置方法sentinel auth-pass mymaster yourpassword注意这个密码是主从复制的密码不是客户端连接使用的密码。第二观察哨兵是否真正感知到拓扑变化有个快速命令redis-cli -p 26379 sentinel master mymaster输出结果里的num-slaves和num-other-sentinels字段可以快速确认哨兵集群的全局视角是否正确。如果num-other-sentinels一直是 0说明哨兵之间没有互通检查bind配置和防火墙策略。回看整个哨兵机制的落地我最大的体感是配置本身不难难在理解它背后的状态机逻辑。你把主观下线、客观下线、选举、复制重定向这条链路在脑子里跑顺了生产环境遇到任何诡异问题都能快速定位到环节。做故障演练尤其重要别等真出事才第一次看哨兵日志那种手足无措的感觉体验过一次就够了。建议每季度在生产环境做一次主库切换演练不仅验证哨兵机制也顺便验证业务侧的连接重连逻辑是否健壮。
返回列表