Redis哨兵集群搭建与自动故障转移实战指南
发布时间:2026/10/5 10:48:16 锦皓数字建站

写这篇东西之前先交代个背景。我接手过一个电商后端项目Redis里存着会话、商品详情缓存和秒杀库存预扣单机部署一直跑得好好的直到某天凌晨一台物理机宕机整个会员体系直接不可用大半夜被电话叫起来应急。那次之后我就把目光锁定在Redis官方的哨兵方案上研究怎么在生产环境里搭一套能够自动故障转移的Redis哨兵集群。这篇文章就是那次实践的完整记录包含配置细节、验证过程和我踩过的坑适合已经接触过Redis、想了解或正在搭建哨兵集群的运维和开发同学参考。1. 先弄清楚哨兵集群到底解决了什么从单机到高可用的演进逻辑很多人一上来就查怎么搭Sentinel但没搞明白背后的设计动机配置里全是复制粘贴出了故障也不知道问题出在哪。所以第一个章节我先把演进过程讲清楚这决定了后面的每一项配置该怎么做。1.1 单机Redis的两个致命问题单机Redis看起来简单用的人多了就暴露两个问题。第一个是读性能瓶颈。一台8核16G的服务器跑满Redis的查询吞吐量大概在10万QPS上下再往上压就会触顶。业务规模增长后热点商品的详情页缓存、用户登录状态查询全部打到同一台机器上延迟开始抖动。第二个是可用性短板。Redis默认是异步刷盘进程崩溃时最多丢失几秒数据机器宕机或交换机故障时Redis直接不可用直到运维赶去机房处理。这个恢复时间通常是以小时计的业务方完全接受不了。1.2 主从复制解决了读和备份但没有解决自动切换主从复制的引入初衷是让读请求分散到从节点同时从节点作为数据的实时备份。Redis的主从复制使用异步机制主库收到写命令后先返回客户端再通过复制流同步给从库。这个方案解决了单点读压力却埋了一个隐患主库宕机后从库虽然持有最新数据但不会自动升级为主库。你需要人工登录从库执行slaveof no one再修改客户端的连接地址整个过程至少几分钟。线上每多一分钟不可用就是实打实的损失。1.3 哨兵Sentinel是守护进程不是代理这正是哨兵出现的理由。哨兵独立于Redis实例运行它可以做三件事监控每个哨兵进程定时向所有Redis实例发送PING命令检查它们是否活着、是否可响应。通知当被监控的Redis实例状态变化时哨兵通过发布订阅机制通知其他哨兵和客户端。自动故障转移当主库被判定下线后哨兵集群会协商出一个新的主库把从库指向到新主库并通知客户端使用新地址。需要特别说明的是哨兵不是Redis的代理网关。客户端不是把请求发给哨兵再由哨兵转发而是先从哨兵获取当前可用的主库地址然后直连主库读写。所以哨兵本身并不承担请求转发它的资源开销非常小。生活化类比主从复制像是你家里装了备用发电机但没装自动切换开关停电了你得自己去拉闸哨兵就是这个自动切换开关加值守电工它帮你盯着电路停电就自动切换还会打电话告诉你已经切好了。2. 搭建前的拓扑规划与核心参数为什么需要奇数个哨兵节点动手之前先想清楚要搭几个节点、每个节点承担什么角色。这一步做得越细后面配置越省心。2.1 推荐的节点拓扑一主两从三哨兵朴素的高可用方案是三台机器每台上跑一个Redis实例共一个主库两个从库在同样的三台机器上再各跑一个哨兵进程。为什么是三因为哨兵要过半才能选出新主三个哨兵允许挂掉一个五个哨兵允许挂掉两个。节点太少无法容错太多纯粹的哨兵节点又增加维护成本三哨兵是生产中最常用的起步配置。实际生产中我建议这样分配节点IP示意Redis实例角色Sentinel角色节点A192.168.1.10mastersentinel-a节点B192.168.1.11slavesentinel-b节点C192.168.1.12slavesentinel-c2.2 Sentinel的工作机制主观下线与客观下线这是核心参数的理解重点我展开讲。每个哨兵节点都在做两件事一是向监控的master发PING二是和其他哨兵节点建立发布订阅通道交换意见。当某个哨兵发现自己发PING得不到master的响应并且持续超过down-after-milliseconds设定的时间它会把这个master标记为主观下线这是单方面的判断不代表master真的挂了可能是网络抖动也可能是这个哨兵自己的网络出问题。只有当一个哨兵把master标记为主观下线后它才会通过Sentinel内部协议询问其他哨兵你们也认为master挂了吗 当同意master下线的哨兵数量达到配置的quorum时这个master才会被标记为客观下线。客观下线是触发故障转移的前提。也就是说一个哨兵认为master不可用还不够必须满足法定数目的哨兵都认为master不可用。这就是哨兵集群比单哨兵更可靠的原因它避免了单点误判。2.3 为什么quorum要设2而不是3很多人第一次配置时把quorum理解成 需要全部哨兵同意这是不对的。quorum更像最低启动线。quorum2意味着三个哨兵中至少两个认为master挂了才开始触发故障转移。quorum3的话任意一个哨兵掉线后剩余两个哨兵再怎么判断都没法达到3故障转移永远不会被触发这显然不合理。如果集群规模是五个哨兵quorum通常设3。原则是quorum应小于等于哨兵总数的多数派这样才能保证在个别哨兵异常时仍然能够完成故障转移。这也是为什么哨兵必须奇数个的根本原因哨兵集群的决策机制基于过半数原则奇数才能保证任何情况下都存在明确的多数。3. 手工搭建全过程从源码包到主从复制的完整配置网络上有很多现成的Redis安装包和使用教程但直接拿来在生产用不现实。这一章我把整个搭建过程完整录入操作系统层面到Redis层面的每一步都附上我实际用的命令。3.1 下载编译与目录规划我使用的是Redis 6.2稳定版这个版本的哨兵机制经过广泛验证资料也多。先把依赖装好再编译安装。# 安装基础编译工具和tclRedis测试依赖 yum install -y gcc gcc-c make tcl # 下载并解压 wget https://download.redis.io/releases/redis-6.2.12.tar.gz tar zxvf redis-6.2.12.tar.gz cd redis-6.2.12 # 编译不做make test节省时间 make -j$(nproc) make install PREFIX/usr/local/redis编译出来的二进制文件在/usr/local/redis/bin里面包含redis-server、redis-sentinel、redis-cli等可执行文件。日常操作时我将这些二进制路径加到了PATH中方便直接用命令。目录规划对我来说很重要遵循配置和数据分开的原则# 在每台节点上执行 mkdir -p /opt/redis/conf mkdir -p /opt/redis/data mkdir -p /opt/redis/logs mkdir -p /opt/redis/run3.2 配置主库基础参数与注意事项主库的配置文件放在/opt/redis/conf/redis-master.conf我又复制了一份到redis-slave.conf作为从库模板。核心配置如下# 监听所有网卡生产环境建议用防火墙做限制比bind多个IP更稳妥 bind 0.0.0.0 port 6379 # 以守护进程方式运行 daemonize yes pidfile /opt/redis/run/redis.pid logfile /opt/redis/logs/redis.log # 数据持久化目录 dir /opt/redis/data # RDB与AOF都开启AOF的appendfsync设为everysec save 900 1 save 300 10 save 60 10000 appendonly yes appendfsync everysec # Master访问密码 requirepass Redis12345这里有两个细节第一bind 0.0.0.0在安全性上确实不如bind 127.0.0.1但哨兵和从库需要通过网络访问它我不能只绑回环地址。生产上更合理的组合是bind内网网卡IP加上操作系统防火墙只放行内网中的特定IP。第二requirepass是Master级别的密码。从库同步数据、哨兵监控都需要这个密码。如果配置了密码却不在从库和哨兵里配置对应的密钥主从复制和哨兵的监控请求都会失败。这个坑我在第六节详细展开。3.3 配置从库指向主库并开启只读从库的配置文件相比主库多三行关键配置bind 0.0.0.0 port 6379 daemonize yes pidfile /opt/redis/run/redis.pid logfile /opt/redis/logs/redis.log dir /opt/redis/data save 900 1 save 300 10 save 60 10000 appendonly yes appendfsync everysec # 从库指向主库 replicaof 192.168.1.10 6379 # 主库密码 masterauth Redis12345 # 确认从库只读 replica-read-only yes # 从库自己的访问密码可和主库保持一致便于统一管理 requirepass Redis12345注意replicaof这一项是连接主库的定位配置如果主库在故障转移后会改变身份哨兵会自动改写从库配置文件里的replicaof指向这是哨兵故障转移功能的一部分。启动之前我习惯先用redis-check-aof检查已有的AOF文件再启动redis-server /opt/redis/conf/redis-master.conf redis-server /opt/redis/conf/redis-slave.conf然后用redis-cli -a Redis12345 info replication验证主从关系重点关注输出中两个字段role:master connected_slaves:2 slave0:ip192.168.1.11,port6379,stateonline,offsetxxx,lag0 slave1:ip192.168.1.12,port6379,stateonline,offsetxxx,lag0当从库的state是online、且偏移量和主库一致时说明全量同步已经完成数据链条是通的。3.4 全量同步与增量同步的区别建立主从关系的第一步是全量重同步从库发送PSYNC主库做一次BGSAVE生成RDB快照传给从库从库接收RDB并加载加载完成后主库持续把缓冲区的写命令推给从库进入增量同步阶段。生产环境中主从复制断开的常见原因有两个网络抖动导致复制积压缓冲区溢出。默认值repl-backlog-size是1MB对高写入场景来说太小。我会调到64MB以上给从库的主库重连留足缓冲时间。主从版本不一致。Redis在高版本中不兼容老版本的复制协议所以主从机器的Redis版本尽量保持一致。4. Sentinel进程的配置与启动每个参数都要能说出理由主从复制搭好之后接下来是哨兵进程。哨兵配置不是照抄每个参数都值得花时间理解。4.1 哨兵配置文件精讲三台节点上分别创建/opt/redis/conf/sentinel.conf内容如下bind 0.0.0.0 port 26379 daemonize yes pidfile /opt/redis/run/sentinel.pid logfile /opt/redis/logs/sentinel.log # 监控的对象名字、地址、quorum sentinel monitor mymaster 192.168.1.10 6379 2 # master访问密码 sentinel auth-pass mymaster Redis12345 # 判定主观下线的时长单位毫秒 sentinel down-after-milliseconds mymaster 5000 # 故障转移的超时时间 sentinel failover-timeout mymaster 30000 # 做故障转移时最多可以同时同步给几个从库 sentinel parallel-syncs mymaster 1逐项解释我的选择sentinel monitor一行定义了哨兵要盯的那个逻辑主库。mymaster是这个主库的逻辑名字所有哨兵节点必须写一致否则它们会认为是两套不同的主库。地址和端口是初始感知的master位置故障转移后会动态变化。最后的2就是quorum。sentinel auth-pass用于让哨兵以认证身份去连Redis。没有它Redis节点开启密码认证后哨兵的监控流程会被拒绝导致误判下线。down-after-milliseconds设5000意味着每个哨兵若连续5秒ping不通master会先给出主观下线的判断。这个值设为5秒属于比较灵敏的配置如果用跨机房网络建议提到10000避免网络抖动引发的误切换。failover-timeout是故障转移的总体时限包括从节点被选为新主后的老主重新加入等逻辑。设30秒足够若超时则会重新尝试。parallel-syncs设为1表示故障转移后同一时间只允许一个从库同步新主库避免新主库刚上位时因为同时服务多个全量同步而卡爆。4.2 启动三个哨兵并用info验证在三台节点上分别启动redis-sentinel /opt/redis/conf/sentinel.conf启动后我用两个命令来验证哨兵集群的健康状态redis-cli -p 26379 info sentinel redis-cli -p 26379 sentinel get-master-addr-by-name mymaster第一条的输出应该有5个关键信息sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_maxlen:0 sentinel_slaves:2当三个哨兵都启动后它们会通过Redis的发布订阅机制互相通信每10秒去master拿一份节点拓扑动态感知彼此的地址。我更喜欢用sentinel master mymaster看详细信息其中包含num-other-sentinels如果等于2说明三个哨兵已经完成互联。4.3 哨兵进程守护systemd的必要性哨兵是高可用体系里最核心的进程如果它自己挂了配套的高可用就无从谈起。生产上不能裸跑redis-sentinel要交给systemd守护。下面是一份我常用的systemd unit文件[Unit] DescriptionRedis Sentinel Afternetwork-online.target Wantsnetwork-online.target [Service] Typeforking ExecStart/usr/local/redis/bin/redis-sentinel /opt/redis/conf/sentinel.conf ExecStop/usr/local/redis/bin/redis-cli -p 26379 shutdown Restartalways RestartSec5 [Install] WantedBymulti-user.target启动并设置为开机自启systemctl daemon-reload systemctl enable redis-sentinel systemctl start redis-sentinel5. 故障转移实测master断电后到底发生了什么配置写完了理论讲得再多也得实际验证。这一节是文章的重头戏记录了完整的故障模拟过程和日志解读。5.1 模拟场景主动杀掉主库进程在三节点集群正常运行时我在节点A上执行redis-cli -p 6379 -a Redis12345 shutdown nosave这步等价于模拟一次主库宕机。执行之后立刻在哨兵节点B上观察日志文件/opt/redis/logs/sentinel.log会看到类似下面的过程sdown master mymaster 192.168.1.10 6379 odown master mymaster 192.168.1.10 6379 #quorum 2/2 new-epoch 1 try-failover master mymaster 192.168.1.10 6379 vote-for-leader 6c0d8a... selected-slave 192.168.1.11:6379 promoted-slave 192.168.1.11:6379 failover-state-send-slaveof-noone slave 192.168.1.11:6379 failover-state-wait-promotion-slave slave 192.168.1.11:6379 promoted-slave 192.168.1.11:6379 switch-master mymaster 192.168.1.10 6379 192.168.1.11 6379这里每一行日志都对应一个关键状态机转换我逐个解读sdown表示当前哨兵主观判断master下线。odown表示收到其他哨兵反馈后已满足quorum2master被确认客观下线故障转移被触发。try-failover表示本哨兵尝试发起一次故障转移。vote-for-leader表示哨兵之间正在投票选举本次故障转移的领导者只有过半数同意的哨兵才有资格执行后续操作。selected-slave表示领导者从所有从库中选择一个作为新主库选择依据包括复制偏移量、优先级、网段等。promoted-slave表示目标从库已被晋升为新主库。switch-master是所有客户端要关注的最重要的日志行它宣告旧master地址已经被新master地址替代。哨兵会把这一事件通过发布订阅通道广播给订阅了switch-master的客户端。5.2 验证新主库与从库拓扑故障转移完成后在节点B上执行redis-cli -p 6379 -a Redis12345 info replication输出应该显示role:master connected_slaves:1 slave0:ip192.168.1.12,port6379,stateonline节点C已经自动执行了replicaof 192.168.1.11 6379它的master_host被重定向到新主库。这个动作由哨兵发起通过向从库发送REPLICAOF命令完成不需要人工介入。在节点B的哨兵上再次执行redis-cli -p 26379 sentinel get-master-addr-by-name mymaster输出应为192.168.1.11 6379说明哨兵的记忆地址已经更新。5.3 老主库恢复后会发生什么这是很多人会忽略的场景。把节点A的Redis进程重新拉起来redis-server /opt/redis/conf/redis-master.conf启动后哨兵会发现旧主库重新上线但它此时的身份已经是从库。哨兵会自动向它下发REPLICAOF 192.168.1.11 6379将其设置为新主的从库。所以不要试图在原Master上做任何手动配置恢复后它要做的第一件事就是向新主库同步数据同步方式取决于断线期间的复制积压缓冲区是否够用。我这里单独强调一个经验老主库恢复后如果复制积压缓冲区不够会触发一次全量同步。对于数据量上G的实例全量同步期间主库会做BGSAVECPU和磁盘IO都可能冲高。高负载业务场景下建议错峰恢复。5.4 数据一致性验证做完故障转移后我还做了缓存数据一致性抽查# 故障前写入的key redis-cli -p 6379 -a Redis12345 get foo # 故障后新写入的key redis-cli -p 6379 -a Redis12345 set bar new-data redis-cli -p 191.168.1.12 -a Redis12345 get bar期望结果是旧key能在新主上查到新key也能通过复制同步到从库。Redis的异步复制机制决定了故障切换瞬间可能有少量写命令没来得及同步到从库从而丢失。这个窗口期通常在毫秒级对不能丢失数据的业务需要在上层做补偿。这也是为什么网上常说说Redis主从复制是最终一致性不是强一致的根本原因。我在方案评审时一定会把这个限制讲给业务方让业务方评估是否在自己的场景里可接受。6. 客户端接入Java应用中连接哨兵集群的正确姿势集群搭好了应用还连老地址就没有意义。客户端接入哨兵集群的原理和连接单机不太一样。6.1 客户端先问哨兵要主库地址哨兵模式下的应用不直接配置Master的IP而是配置哨兵地址和逻辑主库名。以Spring Boot为例配置文件这样写spring: data: redis: sentinel: master: mymaster nodes: - 192.168.1.10:26379 - 192.168.1.11:26379 - 192.168.1.12:26379 password: Redis12345应用启动时Spring Data Redis会随机选一个哨兵节点执行sentinel get-master-addr-by-name mymaster拿到当前主库地址之后所有连接都发往这个主库。客户端启动后与哨兵建立发布订阅连接订阅哨兵事件频道。当master被切换到新节点时哨兵广播switch-master客户端收到后自动释放旧连接池并建立新连接。所以我建议客户端配置中写至少两个哨兵节点防止第一个哨兵不可用时获取主库地址失败。6.2 Lettuce与Redisson的哨兵兼容差异Spring Boot 2.x默认使用Lettuce作为Redis客户端它对哨兵的switch-master事件能自动感知并刷新拓扑。但有一个问题故障切换期间Lettuce的连接池里所有连接到旧Master的连接都会失效如果业务代码没有设置合理的超时和重试首次请求会直接报错。Redisson的哨兵模式则做了一层拓扑更新与重连机制故障转移期间部分命令会短暂超时但框架会自动重建连接。我在高并发秒杀场景里更倾向Redisson因为它对Redis故障变化的感知更主动。这两个客户端核心配置参数注意点参数LettuceRedisson连接超时默认10秒建议调至5秒内默认10秒可调读取超时默认60秒需调默认3秒通常够用拓扑刷新Sentinel模式自动但需确保订阅不丢失自动刷新并支持主从切换回调不过Lettuce在引入Spring Boot后通常采用长连接若主从切换发生时正在执行阻塞命令比如BLPOP那个命令会因为主库已经下线而长时间挂起。业务侧应给Redis命令增加超时控制避免命令无限等待。6.3 故障转移期间客户端会经历什么模拟一次切换在客户端日志里能看到这些问题某几个请求超时、连接池释放旧连接、重建到新Master的连接。如果业务代码没做重试切换期通常有几十毫秒到一两秒的报错窗口。我的建议是业务侧代码尽量对Redis读操作做重试重试间隔200毫秒最多三次。因为故障转移的流失窗口很短重试能大幅降低瞬时错误率。7. 深挖几个容易翻车的配置点从日志看问题根因这一章我先列一个排查链路再逐个说明关键坑。很多人照着文档搭建后遇到问题开始瞎试我们需要有章法地看日志分析原因。7.1 坑一从库配置了masterauth但密码写错现象slave的日志里反复出现MASTER - REPLICA sync started和Authentication error。根因从库连接主库时主库要求密码认证从库配置的masterauth和主库的requirepass不一致。解法统一所有Redis实例的requirepass从库和哨兵里的认证信息必须与主库完全一致。修改密码的场景更是要同时改三处主库配置、从库的masterauth、哨兵的sentinel auth-pass改漏一处就出问题。7.2 坑二哨兵主观下线误判现象master运行完全没有问题但日志里出现sdown后又快速出现-sdown形态像抖动。根因排查链路从心跳报文本身入手。哨兵之间通过Redis的发布订阅频道交换信息而哨兵对master的监控则通过PING命令。三个常见诱因网络分区哨兵节点和master节点所在网络不通但其他节点通某些哨兵会误判。连接数打满master的maxclients达到上限新连接被拒绝哨兵PING连不上就会判定主观下线。大key造成的阻塞主库执行超大集合的删除或迁移操作单线程阻塞超过5秒导致所有哨兵的PING都得不到回复。解法排查时需要同时做三件事。查master日志中有没有阻塞命令用redis-cli info commandstats看是否有慢命令以及通过网络工具确认各节点间延迟抖动。另外把down-after-milliseconds从默认的30秒调整到5秒只是追求灵敏跨机房或大型实例要结合实际情况放宽不建议盲目设小。7.3 坑三故障转移被触发后新主库长时间无法选出现象日志停在try-failover后面没有selected-slave。根因哨兵需要对从库列表做筛选。筛选条件是从库优先级、从库复制偏移量、从库是否在线。如果所有从库都因为某种原因不满足条件故障转移就一直无法完成。最常见的场景是从库的down-after-milliseconds已经过期被标记为不可用而配置里又只有这一个从库。所以主从拓扑中至少要有两个从库不能只备一个。7.4 坑四客户端获取不到新主地址现象故障转移成功后业务仍在连旧主库大量报错。根因分两种情况。一种是客户端启动时配置了缓存的主库地址不使用哨兵进行发现典型的连接方式写死了单点地址另一种是客户端订阅哨兵的发布订阅频道失败收不到switch-master事件。解法确认客户端配置是sentinel模式而不是单点模式。检查客户端的日志中是否有订阅哨兵频道的记录。必要时在故障切换后重启应用实例完成连接重排但这只是应急长期还是靠客户端内置的哨兵发现机制。7.5 坑五从库自动failover被禁用现象故障转移执行时提示-failover-abort-not-elected或 Selected slave ... is not ready for failover。根因有些运维同学在图省事搭建主从时为了不让从库被客户端写数据配置了replica-read-only yes这个不影响故障转移。但把replica-priority改为0的从库永远不会被选为候选主库。我曾经在生产环境把某个从库的replica-priority设成0结果故障转移时它一直不被选中剩下一个从库顶不住流量。排查的依据就是哨兵日志里的 is not ready for failover 提示。如果某个从库确实不希望它被提升为主库保留优先级0是合理的但正常情况下不要动它。8. 待补充的进阶思考哨兵不能解决什么写了一长篇最后我补一点我个人对哨兵集群边界的理解。任何技术方案都有边界提前知道边界方案选型才不会错。哨兵解决的是Redis进程级别的可用性问题。它不能解决磁盘故障导致的数据丢失。AOF和RDB都写在本地磁盘磁盘损坏数据照样丢哨兵只能感知到节点下线。跨机房容灾。如果你的机房整体不可用所有节点都在同一个机房哨兵集群也会整体失联。读性能线性扩展。哨兵模式下只有一个可写的主库从库只能承载读请求写入吞吐量依然受单机限制。如果业务对写并发的要求超过单机瓶颈或者需要更强的数据安全保证应转向Redis Cluster或数据分片方案那是另一个话题了。回到我自己的实践现在这套一主两从三哨兵的架构在我的项目里已经平稳运行了大半年期间真实发生过两次主机宕机都是分钟级自动完成切换业务无感知。每次演练和故障复盘时我都会回顾一遍哨兵日志确认切换链路里每个环节的执行顺序是否符合预期——这个习惯强烈推荐给所有在线业务的负责人。最后分享一个小技巧在开发环境演练故障转移时别直接kill -9master进程先用redis-cli debug sleep 30模拟一个阻塞命令看看哨兵反应。这样既不用真正杀进程又能观察主观下线判断的边界在哪里比直接粗暴kill更能摸清系统的脾性。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。