做后端的人不管有没有正式用过应该都听说过 Redis 哨兵集群。第一反应可能是这玩意是不是就是多搞几个 Redis 实例让其中一个看着另一个其实没那么玄乎但也没那么随便。这篇文章是我在实际搭建和生产环境运维中折腾总结出来的经验从主从复制到三个哨兵节点从配置参数到故障切换实测一步步说清楚。适合已经会基本 Redis 操作、正在评估或上手做高可用方案的同学。看完你会发现哨兵集群的搭建核心不是写配置而是理解它为什么这样设计。1. Redis哨兵到底在解决什么问题1.1 单机Redis永远是悬在头上的风险单独跑一个 Redis性能没问题延迟很低部署也省事。可一旦它挂了整个依赖它做缓存、session、分布式锁、队列的业务全部断掉。更难受的是Redis 很多时候不只是缓存还是系统里唯一能扛住高并发的“真相来源”。主从复制引入之后数据有了备份读写可以分开但主库宕机时从库并不会自动顶上需要人工去执行REPLICAOF或者改客户端配置。半夜报警爬起来手动切换主从这种体验谁干谁知道。哨兵Sentinel就是专门解决这个自动切换问题的。它负责三件事监控 Redis 数据节点是否存活、通过 Pub/Sub 通知客户端当前 Master 是谁、在主节点故障时自动完成一次故障转移把一个健康副本提升为新主节点。它的目标不是分布式存储也不是分片而是高可用。1.2 哨兵自己也要一堆节点不是单点有人觉得搞一个哨兵进程盯着 Redis 就行了吧不行。哨兵本身如果只是单个进程它自己挂了监控就失效这就成了一个更脆弱的单点。所以哨兵必须是集群至少三个实例彼此之间也会互相通信并协商投票。这里的核心概念是quorum。比如配置sentinel monitor mymaster 10.0.0.10 6379 2最后的数字 2 表示至少要有 2 个哨兵认为 Master 主观下线才会触发客观下线和故障转移。三个哨兵节点由多数派决策既避免单个误判也保证单个节点故障时整个哨兵集群仍然能工作。你可能会问为什么不用两个两个哨兵在网络分区时凑不出明确多数很容易两边都等不到响应风险反而大。生产环境里最稳妥的做法是奇数个哨兵正常三节点起步。1.3 哨兵和 Cluster 模式是两回事很多项目聊到“Redis 集群”其实是默认指 Redis Cluster。这个命名上的混淆会让不少人踩坑。哨兵模式和 Cluster 模式解决的是完全不同的问题我把它们放在一起对比一下维度Redis 哨兵Redis Cluster核心目标高可用自动故障转移水平扩展数据分片数据存储所有节点全量复制同一份数据每个主节点只存一部分 slot 的数据写能力只有 Master 可写多个 Master 同时可写扩缩容加从库只增加读能力动态增加/减少分片客户端要求需支持 Sentinel 协议需支持 Cluster 重定向适合场景数据量不大但要求高可用单机内存放不下、需要弹性扩缩容如果你的 Redis 总数据量在单台服务器内存能装下的范围优先考虑哨兵。只有当数据量已经大到一台机器扛不住单靠加内存不划算时才需要 Cluster。这两个不是替代关系很多时候系统中会同时部署 Cluster 集群但每个分片内部又有主从和哨兵做高可用其实是可以叠加的。别再傻傻分不清楚了。2. 搭建哨兵集群前的准备工作2.1 架构规划把端口和服务画清楚我这次搭建用 Docker Compose 来跑好处是干净、可复现坏处是网络 NAT 和心理上要接受“容器即进程”的设定。实际生产裸机部署时思路完全一致只是把容器换成 systemd 管理的 redis-server 进程。规划如下一个 Master两个 Replica三个 Sentinel。数据节点端口分别是 6379Master、6380Replica1、6381Replica2哨兵端口是 26379、26380、26381。之所以用三节点哨兵是因为 quorum 需要多数两个 Replica 是为了故障转移后有从库可用如果只有一主一从主挂掉后从升主再挂就没有备份了。主从架构里Master 处理写请求Replica 实时复制数据平时可以分担读流量。哨兵节点不存业务数据只维护元信息和状态。我把这些服务放到同一个 Docker 网络redis-net里这样容器之间用服务名就能互相访问不用去查 IP。2.2 选 Redis 版本和镜像我用的是redis:7.0官方镜像。7.x 对主从复制的性能做了不少优化官方也已经完全移除了slaveof等老命令统一用replicaof。如果你的历史工程还在用老写法的配置迁移到 7.x 时记得替换。关于 Redis 下载和安装如果你不是用 Docker而是直接从官网下载源码编译建议选 stable 版本不要碰 RC 版。Linux 上记得先装gcc和make解压后在目录里执行make make install PREFIX/usr/local/redis但编译安装通常只是第一步后面还得写系统服务、配环境变量。想快速验证哨兵效果Docker 是效率最高的方式。下面所有步骤我都基于 Docker Compose但配置文件和原理完全适用于裸机。2.3 先把主从复制搭起来哨兵监控的对象是一个主从复制拓扑所以主从复制必须先行。只启动三个独立的 Redis 数据节点但彼此不复制哨兵看着也没意义。一个最小可用的docker-compose.yml长这样version: 3.8 services: redis-master: image: redis:7.0 container_name: redis-master command: [redis-server, --appendonly, yes] ports: - 6379:6379 networks: - redis-net redis-replica-1: image: redis:7.0 container_name: redis-replica-1 command: [redis-server, --appendonly, yes, --replicaof, redis-master, 6379] depends_on: - redis-master ports: - 6380:6379 networks: - redis-net redis-replica-2: image: redis:7.0 container_name: redis-replica-2 command: [redis-server, --appendonly, yes, --replicaof, redis-master, 6379] depends_on: - redis-master ports: - 6381:6379 networks: - redis-net networks: redis-net: driver: bridge--replicaof redis-master 6379里的redis-master是由 Compose 网络自动解析的容器 DNS 名称不需要手动写死容器的动态 IP。这比裸机上写 IP 省事也更符合云原生环境里服务发现的做法。启动后验证一下从库是否真正在同步docker exec redis-replica-1 redis-cli info replication如果看到role:slave并且master_link_status:up说明主从复制已经通了。先用普通客户端向 Master 写入数据再到 Replica 上查能查到就说明数据同步链路没问题。3. 三个哨兵实例的配置与启动3.1 核心配置项逐个拆解哨兵配置不需要花里胡哨核心就这几行。我用/usr/local/etc/redis/sentinel.conf这个标准路径内容如下port 26379 daemonize no dir /tmp sentinel monitor mymaster redis-master 6379 2 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 10000 sentinel parallel-syncs mymaster 1port 26379是哨兵自己监听端口。daemonize no很重要因为容器里必须让 redis-server 在前台运行否则容器启动后立刻退出。sentinel monitor这一行是核心mymaster是自定义的主节点逻辑名称客户端连接时要用它redis-master是当前 Master 的地址6379是端口最后的2就是前面说的 quorum表示至少两个哨兵同时判定 Master 主观下线才会触发客观下线判断。down-after-milliseconds表示哨兵在多少毫秒内没收到有效回复就把该节点标记为主观下线。我设 5000 毫秒是方便演示生产环境一般设 10000 到 30000太短容易误判网络抖动就直接触发切换太长了故障恢复又慢。failover-timeout控制故障转移的超时时间。它包含了很多子阶段比如选举 Leader、向从库发起复制、更新配置。设 10000 毫秒在我这套本地演示环境足够了。parallel-syncs是当新 Master 被选出来后同时允许几个 Replica 去同步新 Master 的数据。1 说明每次只有一个从库做全量同步避免多个从库同时同步把新主节点的磁盘 IO 打满。生产环境如果从库很多可以适当调大但要留意主节点压力。如果 Redis 配置了密码还需要额外加一行sentinel auth-pass mymaster yourpassword哨兵和 Redis 之间通信需要使用这个认证信息。没有密码时 Redis 会打 warning本地演示无所谓生产环境必须配。3.2 用 Docker Compose 一键拉起哨兵集群有了上面的配置思路我直接在 Compose 文件的command里把参数写全这样不用额外生成三个配置文件。最终版docker-compose.yml如下version: 3.8 services: redis-master: image: redis:7.0 container_name: redis-master command: [redis-server, --appendonly, yes] ports: - 6379:6379 networks: - redis-net redis-replica-1: image: redis:7.0 container_name: redis-replica-1 command: [redis-server, --appendonly, yes, --replicaof, redis-master, 6379] depends_on: - redis-master ports: - 6380:6379 networks: - redis-net redis-replica-2: image: redis:7.0 container_name: redis-replica-2 command: [redis-server, --appendonly, yes, --replicaof, redis-master, 6379] depends_on: - redis-master ports: - 6381:6379 networks: - redis-net sentinel-1: image: redis:7.0 container_name: sentinel-1 command: redis-server --port 26379 --sentinel --sentinel monitor mymaster redis-master 6379 2 --sentinel down-after-milliseconds mymaster 5000 --sentinel failover-timeout mymaster 10000 --sentinel parallel-syncs mymaster 1 depends_on: - redis-master - redis-replica-1 - redis-replica-2 ports: - 26379:26379 networks: - redis-net sentinel-2: image: redis:7.0 container_name: sentinel-2 command: redis-server --port 26379 --sentinel --sentinel monitor mymaster redis-master 6379 2 --sentinel down-after-milliseconds mymaster 5000 --sentinel failover-timeout mymaster 10000 --sentinel parallel-syncs mymaster 1 depends_on: - redis-master - redis-replica-1 - redis-replica-2 ports: - 26380:26379 networks: - redis-net sentinel-3: image: redis:7.0 container_name: sentinel-3 command: redis-server --port 26379 --sentinel --sentinel monitor mymaster redis-master 6379 2 --sentinel down-after-milliseconds mymaster 5000 --sentinel failover-timeout mymaster 10000 --sentinel parallel-syncs mymaster 1 depends_on: - redis-master - redis-replica-1 - redis-replica-2 ports: - 26381:26379 networks: - redis-net networks: redis-net: driver: bridge注意在 Docker 环境里如果想把宿主机端口映射到不同的端口比如宿主机 26380 映射到容器的 26379哨兵之间通信没有影响因为它们走的是容器网络内部。这个配置里三个哨兵监听的都是容器内的 26379只是为了从宿主机用不同端口访问才做了三个 port 映射。执行启动docker compose up -d启动后查看状态docker compose ps docker exec sentinel-1 redis-cli -p 26379 sentinel master mymaster如果返回mymaster和对应 Master 地址、端口说明哨兵已经在监控了。再看哨兵之间是否互相感知docker exec sentinel-1 redis-cli -p 26379 sentinel sentinels mymaster能列出另外两个哨兵节点就说明哨兵集群之间已经通信完成。3.3 动动手指模拟一次真实故障转移搭建好之后不做一次故障演练不踏实。我直接在宿主机把 Master 容器停掉docker stop redis-master然后观察哨兵日志docker logs sentinel-1 --tail 50 -f正常会看到类似下面的日志序列sdown master mymaster 172.20.0.2 6379 odown master mymaster 172.20.0.2 6379 #quorum 2/2 try-failover master mymaster elected-leader master mymaster selected-slave slave 172.20.0.4:6379 172.20.0.4 6379 promoted-slave slave 172.20.0.4:6379 172.20.0.4 6379 switch-master mymaster 172.20.0.2 6379 172.20.0.4 6379这就是一次完整的故障转移从主观下线判断到确认客观下线再到选举 Leader、挑选从库、提升为新 Master最后把整个 Sentinel 集群的视角切换到新 Master。整个过程在我设的 5 秒主观下线时间下大概 10 到 15 秒完成。确认当前集群视角下的 Masterdocker exec sentinel-1 redis-cli -p 26379 sentinel get-master-addr-by-name mymaster返回结果会指向redis-replica-1或者redis-replica-2。此时再往新 Master 写入数据往旧 Master 的端口写入会失败因为旧容器已经停了。重启旧 Masterdocker start redis-master稍等片刻它会自动变成新 Master 的从库不需要人工干预。至此高可用闭环已经成立。4. 故障转移背后的原理搞懂这个才是真会了4.1 主观下线和客观下线哨兵判定节点不可用分两个阶段。每次哨兵都会定期向所有数据节点发送PING如果在down-after-milliseconds内没收到有效回复当前哨兵会先标记该节点为主观下线。这只是一个哨兵的“个人判断”并不代表节点真的挂了。当标记主观下线的哨兵数量达到 quorum 时就会进入客观下线状态。客观下线不是由单个哨兵拍板而是多个哨兵协商后的结论。这一步能有效避免单个哨兵网络抖动导致的误切换。所以 quorum 不可以大于哨兵节点数一般建议小于哨兵总数的一半以上。4.2 Leader 选举和从库挑选策略确认客观下线后哨兵集群会选出一个 Leader 负责执行故障转移。这个选举用的是一个类 Raft 的过程每个哨兵都有投票权得票数超过半数的成为 Leader。这也是为什么总节点数必须是奇数更稳三节点时最少两票五节点时最少三票偶数节点在某些分区场景下会陷入平票。Leader 选出来之后需要从所有健康的 Replica 里挑一个当成新 Master。挑选顺序有优先级过滤掉被主观下线的从库和长时间没有响应复制命令的从库。比较replica-priority配置数字越小优先级越高。如果优先级一样比较复制偏移量偏移量越大说明数据越新优先选它。如果还一样比较 Run ID字典序小者胜。整个过程看起来短但每个阶段都有超时控制。failover-timeout就是用来兜底的某个阶段卡住超过这个时间Leader 会放弃或者重试。4.3 应用层客户端到底怎么感知新 Master故障转移后的一个重要问题业务客户端还连着旧 Master怎么办如果你用的是普通连接池比如直接把 Redis 地址配在 Spring Boot 里那故障转移后必须改配置重启服务这在高可用场景是不可接受的。正确做法是使用支持哨兵的客户端。以 Java 的 Jedis 为例可以用JedisSentinelPoolSetString sentinels new HashSet(Arrays.asList(10.0.0.11:26379, 10.0.0.12:26379)); JedisSentinelPool pool new JedisSentinelPool(mymaster, sentinels);客户端拿到mymaster这个名字后会向哨兵要当期 Master 地址同时订阅 Sentinel 的switch-master频道。一旦发生故障转移客户端会收到通知自动重新创建到新 Master 的连接。Spring Boot 里的配置也类似spring.redis.sentinel.mastermymaster spring.redis.sentinel.nodes10.0.0.11:26379,10.0.0.12:26379,10.0.0.13:26379生产环境你只能把哨兵地址告诉客户端不要写死 Master 的 IP否则“高可用”只对运维生效对业务代码完全无效。5. 常见问题与避坑指南5.1 哨兵日志里一直报-sdown slave是坏事吗不一定。当 Replica 短暂断开连接时哨兵会先记sdown如果重新连接成功就会记-sdown。这些是正常状态变更。怕的不是sdown而是长期odown且日志里出现failover-abort-no-good-slave说明找不出健康从库来提升。所以生产环境至少保持两个从库如果只有一个从库它一旦出问题故障转移直接失败。5.2 主从切换后客户端一直写旧 Master 怎么办大概率是客户端没用 Sentinel 协议或者在代码里缓存了 Master 地址。我见过不少项目拆了 Redis 哨兵代码里还在 new 一个固定 IP 的连接池故障转移效果约等于零。解决方法是统一改成 Sentinel-aware 客户端或者在你的连接池外围加一层动态解析。很多语言都有现成方案比如 Python 的redis.sentinel.SentinelJava 的Lettuce和Jedis的 Sentinel 模式Go 的go-redis也支持FailoverClient。直接用别自己撸。5.3 容器里的哨兵节点之间互相找不到如果你用的是 Docker Compose要注意所有容器必须在同一个自定义网络里并且哨兵配置里的 Master 地址要写服务名而不是127.0.0.1。比如redis-master:6379。如果写在宿主机端口映射上哨兵从容器内部访问127.0.0.1:6379是访问不到 Master 的。另外跨主机部署时哨兵会把自己看到的 IP 广播给其他哨兵。如果容器网络是 NAT需要显式声明sentinel announce-ip和sentinel announce-port否则其他哨兵拿着一个不可达的地址来连接就会出现“哨兵之间无法互相感知”的问题。5.4 哨兵集群也要监控最后说一个容易忽略的点哨兵集群本身也需要被监控。它虽然负责 Redis 高可用但它自己也是进程也会崩溃。如果三个哨兵里挂了一个影响不大挂了两个哨兵集群没法形成多数派故障转移就失效了。所以建议对26379端口做存活检查和sentinel master mymaster的健康探测一旦发现哨兵异常要及时修复。我个人在实际操作中的体会是哨兵集群搭建的难点从来不是写配置文件而是想清楚每个参数背后的语义以及你的业务客户端是否真的具备故障转移感知能力。照着这个思路搭一套再演练一次杀进程你对 Redis 高可用的理解会比读十篇文档都深。
阅读完成 · 觉得有帮助?