不少人搭过Redis主从复制,觉得“数据有备份了”就算高可用了。直到某天凌晨主节点突然宕机,自己睡眼惺忪地登录服务器,手动执行slaveof no one、再让其他从节点重新指向新主,折腾完天都快亮了,才意识到这离“高可用”还差着十万八千里。Redis哨兵(Sentinel)就是来解决这个问题的:它帮你盯着主从拓扑,主节点一旦掉线,自动完成新主选举和从节点重指向,整个过程不用人碰。本文我用Docker把一套“一主两从三哨兵”的最小高可用架构完整跑了一遍,从网络规划、配置编写到故障切换演练,全部记录在案,连踩过的坑一起打包分享,适合刚接触Redis高可用、想快速验证Sentinel机制的读者。
这套环境我在Docker里反复搭过、拆过、破坏过好几次,整体跑通后我对“哨兵到底在干什么”的理解才算真正落地。下面的内容纯按我的实际操作顺序来写,你可以直接照抄,也可以根据自己环境调整。
1. 为什么需要哨兵:主从复制时代的高可用困境
1.1 主从复制解决了数据冗余,但没解决“谁来切换”
主从复制是Redis高可用的第一步。主库负责写,从库通过replicaof/slaveof命令异步同步数据,读流量可以分摊到从库,主库出问题时数据还有副本兜底。听起来不错,但有个很尴尬的问题:如果主库进程崩溃、机器断电,或者网络分区把主库隔开了,从库只会傻傻地等,不会自己转正,也不会自动把新主信息告诉客户端。业务还在往旧主地址上写数据,写入要么失败,要么超时,等到运维人工介入,服务已经中断了十几分钟甚至更久。
这种“被动等待人工”的状态在生产环境里是致命的。我见过不止一次,因为切换不及时、切换时手抖选错从库,导致数据不一致甚至丢数据的例子。主从复制像是给系统上了保险,但理赔的时候还需要人工跑一趟流程,保险的价值就打了折扣。
1.2 哨兵到底在做什么:监控、通知、自动故障转移
哨兵本质上也是一个Redis服务,只不过跑在特殊模式下(redis-server --sentinel)。它做四件事:
- 监控:持续检查主库和从库是否在线,通过PING和INFO命令维持心跳。
- 通知:当被监控的Redis实例状态变化时,哨兵之间通过内部频道互相通报。
- 自动故障转移:主库被判定为客观下线后,哨兵会在存活从库中选出一个提升为新主,并让其他从库改为同步新主。
- 配置提供:客户端可以连上哨兵,询问当前主库的地址是谁,而不是把主库地址硬编码在客户端配置里。
这里最核心的价值是“自动转移”加“客户端发现”。业务不用写死主库IP,而是启动时先问哨兵“现在谁是老大”,拿到地址后再连接。主库切换了,客户端重启后拿到的就是新主地址。
1.3 为什么用Docker来搭这套环境
我在本机用原生方式装过Redis,也在生产环境手工部署过哨兵,但Docker的体验还是最顺手的。原因有三点:
- 环境一致:同事之间共享一个compose文件,跑出来的拓扑一模一样,不会出现“我本机可以你不行”的尴尬。
- 快速重建:配置改坏了、数据写乱了,删掉容器重来,一分钟恢复初始状态,特别适合反复演练故障场景。
- 网络可控:通过自定义bridge网络,给每个容器分配固定IP,模拟多机部署的效果非常方便。
当然Docker也带来了新的麻烦,比如网络模式的选择、容器重启后的状态保持、端口映射与哨兵通告地址的冲突,这些我后面会专门讲一遍。
2. 哨兵架构拆解:一主两从三哨兵的设计逻辑
2.1 为什么最少要三个哨兵节点
很多人看过文档都说“哨兵要奇数个”,但未必清楚背后的投票机制。哨兵判定主库故障分成两步:单个哨兵发现联系不上主库,标记为“主观下线”(sdown);随后哨兵之间互相确认,当同意下线的哨兵数量达到配置里的quorum值,才升级为“客观下线”(odown)。
客观下线只是“可以开始故障转移”的条件之一。真正要执行故障转移时,哨兵之间还要进行一次Leader选举,相当于选出一个“总指挥”来负责切换。这个选举要走多数派投票,也就是说候选哨兵必须拿到超过半数的选票才能当选。如果整个集群只有2个哨兵,投票结果天然容易僵持;而3个哨兵时,最多允许1个哨兵掉线,剩下2个依然能达成多数派,决策流程才能继续走下去。
所以“3个哨兵”是在保证容错能力和资源成本之间的平衡点。4个也能跑,但配置复杂度和节点成本上升,容错能力却没有本质提升,生产环境大多数场景下“一主两从三哨兵”就是性价比较高的经典组合。
2.2 数据流与角色分工:谁在跟谁说话
这套拓扑里的角色和通信关系理顺了,后面配置才不会乱。主库(master)负责写,从库(replica)异步同步主库数据并分担读流量。三个哨兵(sentinel)之间通过Redis的发布订阅频道(__sentinel__:hello)互相发现,定时交换彼此的信息;同时每个哨兵都保持着到主库和所有从库的TCP连接,用于发送PING、拉取INFO。
当主库心跳超时,哨兵发起故障转移后,它会向从库发送replicaof new_master命令,把从库重新指向新主,旧主恢复后也会被哨兵用同样的方式归队。整个切换过程中,客户端如果配置了哨兵发现机制,在旧主不可达时会重新向哨兵查询新主地址,无需手动干预。
2.3 容器编排拓扑:一张表看清角色与IP分配
我建议的容器拓扑如下表,所有容器都放在同一个自定义bridge网络里,并使用固定IP,避免重启后IP漂移。
| 容器名 | 角色 | 容器内端口 | 固定IP | 数据目录 |
|---|---|---|---|---|
| redis-master | 主库 | 6379 | 172.20.0.10 | ./master/data |
| redis-slave-1 | 从库1 | 6379 | 172.20.0.11 | ./slave1/data |
| redis-slave-2 | 从库2 | 6379 | 172.20.0.12 | ./slave2/data |
| sentinel-1 | 哨兵1 | 26379 | 172.20.0.20 | ./sentinel1/data |
| sentinel-2 | 哨兵2 | 26379 | 172.20.0.21 | ./sentinel2/data |
| sentinel-3 | 哨兵3 | 26379 | 172.20.0.22 | ./sentinel3/data |
所有容器都不映射端口到宿主机,只在Docker内部网络互通。这样做的好处是彻底绕开端口映射带来的“地址通告错乱”问题,业务容器只要加入同一个网络,就能直接用redis-master:6379这种域名访问集群。如果你需要从宿主机外部调试,后面我会单独说怎么处理。
3. Docker网络规划:自定义Bridge、固定IP与端口映射的取舍
3.1 为什么不用host网络
Docker提供了多种网络模式,很多人图省事直接network_mode: host,让容器和宿主机共享网络栈。在Linux服务器上这确实省心,哨兵、Redis都直接用宿主机IP通信,没有地址转换,也不存在NAT问题。但有两个隐患:
- Docker Desktop(macOS和Windows)不支持host网络,你在本机跑会直接报错。
- host模式下容器端口和宿主机端口完全混在一起,如果宿主机上还跑着其他Redis实例,端口冲突会让你焦头烂额。
所以我更推荐自定义bridge网络。它相当于给容器搭了一个局域网,容器之间可以用IP或容器名直接通信,和外部隔离得比较干净,也可以随时拆掉重建,适合本地演练和CI环境。
3.2 固定IP vs 容器名解析,我为什么选固定IP
在同一个Docker compose网络里,容器名本身就是可解析的域名,比如在redis-slave-1容器里可以直接ping redis-master。乍一看,哨兵配置里的sentinel monitor mymaster redis-master 6379 2也能工作,因为哨兵容器能解析容器名。
但在实际操作中,我发现固定IP更让人安心。原因在于哨兵会把发现的从库、其他哨兵、以及当前选举世代(epoch)等信息写进自己的配置文件。如果master容器被删除重建,它的IP可能变了,而哨兵配置里保存的known-replica等信息可能还指向旧IP,导致拓扑错乱。固定IP让这些动态记录始终指向同一批地址,排查问题时少一个变量。
3.3 自定义网络的子网选择与避坑
创建一个自定义网络时需要手动指定子网,比如我用的是172.20.0.0/24。这里有个前提:宿主机现有网络不能和这个网段冲突,否则容器访问局域网资源时会出现路由混乱。我自己就遇到过宿主机局域网正好在172.20.x.x段的情况,容器内连公司内网服务全部失败,排查了很久才发现是网段撞车。建议先用ip addr或ifconfig看一眼宿主机网段,再挑一个不冲突的私网段。
配置compose时,网络定义要写成这样:
networks: redis-sentinel-net: driver: bridge ipam: config: - subnet: 172.20.0.0/24然后每个服务里通过ipv4_address指定固定IP。这样整个网络拓扑一目了然,也方便后续在哨兵配置或客户端配置里直观引用。
4. 三个配置文件的完整编写:从主从到哨兵的参数拆解
4.1 主从Redis配置:容器里必须改的几个点
先看主库的master/redis.conf:
bind 0.0.0.0 port 6379 protected-mode no appendonly yes appendfsync everysec dir /data和宿主机部署相比,容器里有两个点必须注意:
bind 0.0.0.0是必须的。如果按很多教程默认只绑定127.0.0.1,容器外部(包括其他容器)根本无法连接这个Redis。protected-mode在非回环连接时会拦截请求,必须显式设为no。其实如果配置了密码,也可以保持默认并加上requirepass,但初学阶段建议先关掉,跑通后再加密码。
从库配置文件类似,只是多了一行replicaof指令。以slave1/redis.conf为例:
bind 0.0.0.0 port 6379 protected-mode no replicaof 172.20.0.10 6379 appendonly yes appendfsync everysec dir /data这里我把replicaof写死成了master的固定IP172.20.0.10。前面强调固定IP的价值就在这里:即使容器重建、从库重启,这个地址仍然有效。dir /data配合Volume挂载,让RDB和AOF文件持久化到宿主机,避免容器销毁后数据全部蒸发。
4.2 哨兵配置:核心参数逐个拆开讲
哨兵配置文件比Redis主从配置稍复杂一点,但核心参数就那么几个。下面是sentinel1/sentinel.conf,另外两个哨兵除了端口一样,内容和这个基本一致(我稍后会说明差异):
bind 0.0.0.0 port 26379 protected-mode no dir /data sentinel monitor mymaster 172.20.0.10 6379 2 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 30000 sentinel parallel-syncs mymaster 1逐行解释一下:
sentinel monitor:监控对象名叫mymaster,目标主库地址是172.20.0.10:6379,最后的2是quorum。quorum=2表示至少两个哨兵都认为主库主观下线,才确定它是客观下线。down-after-milliseconds:哨兵连续多少毫秒联系不上目标就认为它主观下线。我为了演练方便设成了5000毫秒,生产环境建议设大一点,比如30000毫秒,避免网络抖动就触发切换。failover-timeout:故障转移的总超时时间。超时后如果还没完成,哨兵会重新发起一轮选举。parallel-syncs:新主上位后,同时允许多少个从库与新主做全量同步。设成1的好处是逐个同步,避免新主瞬间被多个从库的全量同步请求压垮;如果从库不多,也可以设成2或3。
另外两个哨兵的配置几乎一样,唯一要注意的是如果哨兵文件里有sentinel known-replica这类动态信息,它们会各自记录自己的观察视角,无需手动干预。
4.3 Docker Compose编排:一份可以直接抄的模板
目录结构我建议这样组织:
redis-sentinel-demo/ ├── master/ │ ├── redis.conf │ └── data/ ├── slave1/ │ ├── redis.conf │ └── data/ ├── slave2/ │ ├── redis.conf │ └── data/ ├── sentinel1/ │ ├── sentinel.conf │ └── data/ ├── sentinel2/ │ ├── sentinel.conf │ └── data/ ├── sentinel3/ │ ├── sentinel.conf │ └── data/ └── docker-compose.yml对应的docker-compose.yml如下:
version: "3.8" services: redis-master: image: redis:7.0 container_name: redis-master command: ["redis-server", "/usr/local/etc/redis/redis.conf"] volumes: - ./master/redis.conf:/usr/local/etc/redis/redis.conf - ./master/data:/data networks: redis-sentinel-net: ipv4_address: 172.20.0.10 redis-slave-1: image: redis:7.0 container_name: redis-slave-1 command: ["redis-server", "/usr/local/etc/redis/redis.conf"] volumes: - ./slave1/redis.conf:/usr/local/etc/redis/redis.conf - ./slave1/data:/data depends_on: - redis-master networks: redis-sentinel-net: ipv4_address: 172.20.0.11 redis-slave-2: image: redis:7.0 container_name: redis-slave-2 command: ["redis-server", "/usr/local/etc/redis/redis.conf"] volumes: - ./slave2/redis.conf:/usr/local/etc/redis/redis.conf - ./slave2/data:/data depends_on: - redis-master networks: redis-sentinel-net: ipv4_address: 172.20.0.12 sentinel-1: image: redis:7.0 container_name: sentinel-1 command: ["redis-sentinel", "/usr/local/etc/redis/sentinel.conf"] volumes: - ./sentinel1/sentinel.conf:/usr/local/etc/redis/sentinel.conf - ./sentinel1/data:/data depends_on: - redis-master - redis-slave-1 - redis-slave-2 networks: redis-sentinel-net: ipv4_address: 172.20.0.20 sentinel-2: image: redis:7.0 container_name: sentinel-2 command: ["redis-sentinel", "/usr/local/etc/redis/sentinel.conf"] volumes: - ./sentinel2/sentinel.conf:/usr/local/etc/redis/sentinel.conf - ./sentinel2/data:/data depends_on: - redis-master - redis-slave-1 - redis-slave-2 networks: redis-sentinel-net: ipv4_address: 172.20.0.21 sentinel-3: image: redis:7.0 container_name: sentinel-3 command: ["redis-sentinel", "/usr/local/etc/redis/sentinel.conf"] volumes: - ./sentinel3/sentinel.conf:/usr/local/etc/redis/sentinel.conf - ./sentinel3/data:/data depends_on: - redis-master - redis-slave-1 - redis-slave-2 networks: redis-sentinel-net: ipv4_address: 172.20.0.22 networks: redis-sentinel-net: driver: bridge ipam: config: - subnet: 172.20.0.0/24有两个细节值得说明。第一,哨兵镜像也可以直接用redis:7.0,只是启动命令换成了redis-sentinel。第二,depends_on只能保证容器启动顺序,不能保证Redis主库真正就绪,但从库的replicaof会自动重连,所以就算顺序错了也能自愈。如果你追求更严谨,可以在compose里加healthcheck,这里为了保持配置简洁就不展开了。
5. 启动编排与拓扑验证:让三组容器各就各位
5.1 启动前的权限检查
一个很容易翻车的点是文件权限。哨兵进程在运行时会把选举纪元(epoch)、已发现的从库和哨兵列表写回sentinel.conf,所以挂载进容器的配置文件必须可写。如果宿主机上的sentinel.conf权限是只读,启动后日志里会报Can't rewrite config file之类的错误,而且你会发现哨兵之间始终无法正确发现彼此。
建议启动前先执行一次:
chmod -R 777 ./sentinel1 ./sentinel2 ./sentinel3 ./master ./slave1 ./slave2虽然777不够严谨,但本地演练完全够用,重点是先把环境跑起来。
5.2 启动命令与日志检查
一切就绪后,在redis-sentinel-demo目录下执行:
docker compose up -d容器全部启动后,先看主从是否正常工作:
docker exec redis-master redis-cli info replication正常输出里会显示role:master,下面列出两个从节点的IP和端口:
# Replication role:master connected_slaves:2 slave0:ip=172.20.0.11,port=6379,state=online,offset=... slave1:ip=172.20.0.12,port=6379,state=online,offset=...如果connected_slaves显示为0,多半是从库的replicaof配置写错了,或者主库bind 0.0.0.0没配,从库根本连不上主库。
5.3 数据同步验证:写主库、读从库
再做一次最简单的数据验证。在主库写入一个key:
docker exec redis-master redis-cli set hello world然后在两个从库上读:
docker exec redis-slave-1 redis-cli get hello docker exec redis-slave-2 redis-cli get hello如果都能返回world,说明主从复制链路正常。这一步看似简单,却是后面故障转移验证的基础——如果连正常状态下数据都不同步,切换后必然丢数据,排查起来更痛苦。
5.4 哨兵视角:如何确认三兄弟都上线了
主从没问题后,随便挑一个哨兵容器查询它视角里的拓扑:
docker exec sentinel-1 redis-cli -p 26379 sentinel master mymaster docker exec sentinel-1 redis-cli -p 26379 sentinel replicas mymaster docker exec sentinel-1 redis-cli -p 26379 sentinel sentinels mymastersentinel master mymaster输出里的ip和port字段,就是哨兵眼中当前主库的地址,应该是172.20.0.10和6379。sentinel replicas会列出两个从库,sentinel sentinels会看到另外两个哨兵地址。如果这里显示的数量不对,大概率是哨兵之间的protected-mode没关,或者bind没绑对,导致哨兵之间无法互通。
6. 故障转移演练:手动杀掉Master,看哨兵如何接管
6.1 演练前的配置调整
为了不用盯着日志干等60秒,我把down-after-milliseconds调成了5000毫秒。也就是说,主库断连5秒后,哨兵就会开始判断主观下线。演练开始前,先确认当前主库确实是172.20.0.10。
6.2 模拟主库宕机
直接停掉主库容器:
docker stop redis-master注意这里用的是stop而不是kill,两者都行,stop更温和一点。主库停掉后,大概几秒钟内,三个哨兵都会陆续在日志里打出sdown标记:
docker logs sentinel-1 2>&1 | tail -50你会看到类似下面这样的日志序列:
+sdown master mymaster 172.20.0.10 6379 +odown master mymaster 172.20.0.10 6379 #quorum 2/2 +new-epoch 1 +try-failover master mymaster 172.20.0.10 6379 +vote-for-leader 9d539e3... 1 +elected-leader master mymaster 172.20.0.10 6379 +failover-state-select-slave master mymaster 172.20.0.10 6379 +selected-slave slave 172.20.0.11:6379 172.20.0.11 6379 @ mymaster 172.20.0.10 6379 +promoted-slave slave 172.20.0.11:6379 172.20.0.11 6379 @ mymaster 172.20.0.10 6379 +failover-state-reconf-slaves master mymaster 172.20.0.10 6379 +slave-reconf-sent slave 172.20.0.12:6379 172.20.0.12 6379 @ mymaster 172.20.0.10 6379 +slave-reconf-inprog slave 172.20.0.12:6379 172.20.0.12 6379 @ mymaster 172.20.0.10 6379 +slave-reconf-done slave 172.20.0.12:6379 172.20.0.12 6379 @ mymaster 172.20.0.10 6379 +failover-end master mymaster 172.20.0.10 6379 +switch-master mymaster 172.20.0.10 6379 172.20.0.11 6379这段日志值得一行行读懂。sdown变成odown说明达到了quorum=2;elected-leader说明某个哨兵竞选成功,成为这次故障转移的执行者;selected-slave显示它选中了172.20.0.11作为新主,也就是原来的从库1;switch-master标志着最终完成切换,新主地址已经更新。
6.3 验证新主:谁是新的老大
切换完成后,在任意哨兵上执行:
docker exec sentinel-2 redis-cli -p 26379 sentinel master mymaster输出的ip字段应该已经变成172.20.0.11。再进新主库确认角色:
docker exec redis-slave-1 redis-cli info replication可以看到role:master,而且它的从库列表里出现了原来的从库2(172.20.0.12)。原主库172.20.0.10此刻处于离线状态,自然不在列表里。
6.4 旧主恢复:它会自动变成新主的从库
这是我个人觉得哨兵最神奇的一步。把旧主库拉起来:
docker start redis-master等几秒后,再进去看它的状态:
docker exec redis-master redis-cli info replication你会发现它已经不再是主库,而是变成了新主(172.20.0.11)的从库:
role:slave master_host:172.20.0.11 master_port:6379整个过程没有任何人工介入。哨兵在旧主恢复连接后,自动向它发送了replicaof命令,把它归队到新主底下。数据也会从新主同步过来。这种“自动归队”机制是生产环境高可用的关键,很多人在手工管理主从时最容易漏掉这一步。
6.5 客户端视角:接入哨兵而不是直连主库
这次演练暴露了一个重要事实:业务客户端如果要享受故障转移带来的好处,就不能把连接地址硬编码成某个Redis实例的IP,正确的做法是配置哨兵地址。以Spring Boot为例:
spring: redis: sentinel: master: mymaster nodes: sentinel-1:26379,sentinel-2:26379,sentinel-3:26379客户端启动时会向哨兵询问当前主库地址,之后还会持续监听哨兵通知。主库一旦切换,客户端会自动感知并重新建立连接。如果不这么做,即使哨兵完成了故障转移,你的业务依然在往旧主IP上打数据,高可用等于白搭。
7. 高频踩坑复盘:容器化哨兵的常见意外与排查链路
7.1 坑一:哨兵通告的地址让外部客户端连不上
我一开始把Redis容器映射到宿主机端口(比如6379映射成16379),然后本地用客户端连接,结果发现哨兵返回的主库地址是172.20.0.10:6379,这个IP在宿主机上根本不可达,连接直接失败。
原因在于:哨兵通过INFO命令拿到的主库地址是容器内的IP和端口,它不会知道你做了端口映射。要彻底解决这个问题,最好的方案是让所有需要访问Redis的业务容器与Redis集群置于同一个Docker网络,直接走内部地址。如果你一定要从宿主机外部访问,可以考虑在Linux下使用host网络模式,或者额外配置replica-announce-ip等参数,但这些方案各有局限,远不如网络内互通来得干净。
7.2 坑二:哨兵状态随容器销毁一起蒸发
哨兵会把运行中发现的从库列表、其他哨兵地址、当前epoch等信息写回配置文件,这是它恢复状态的关键。如果像我第一次那样,只挂载了配置文件的初始副本,没有给容器内数据目录做持久化,容器销毁重建后,哨兵会忘掉之前发现的所有从库和哨兵节点,整个集群的感知能力直接归零。
解决办法很简单:像示例compose里那样同时挂载./sentinelN/data:/data,并在sentinel.conf中指定dir /data。这样即使容器重建,哨兵也能从磁盘恢复之前的观察状态。
7.3 坑三:protected-mode没关,哨兵之间互相“失联”
有一个非常隐蔽的排查经历:主从都正常,但sentinel sentinels mymaster始终只返回自己,不显示其他哨兵。我一开始以为是网络问题,后来进入容器里手动ping、telnet都没问题,最后才想起来Redis默认protected-mode yes,非回环地址的请求会被直接拒绝。
哨兵之间的通信、哨兵对主从库的心跳,全部来自非回环IP,所以protected-mode no必须设置。如果出于安全考虑不想全局关闭,也可以同时配置bind和密码认证,但对本地演练环境来说,直接关掉是最省事的。
7.4 坑四:哨兵一直报sdown但迟迟不触发切换
这是我最开始调quorum参数时踩的坑。我把quorum设成了3,但演练时只停了主库,三个哨兵都能检测到主库掉线,按理说应该达成客观下线。结果日志里只有sdown,迟迟不出现odown,更别说后续的switch-master了。
排查链路是这样的:先看哨兵日志,确认是否出现odown;如果没有,说明哨兵之间在“该不该下线”这件事上没有达成一致。检查quorum配置后我发现,虽然三个哨兵都观察到了主库掉线,但quorum=3要求“三个哨兵都确认”,可其中一个哨兵容器因为网络重启过、配置文件丢失,早已不参与投票,实际只有两个有效哨兵,永远凑不齐3票。后来我把quorum调回2,同时修复了那个哨兵的持久化问题,切换才顺利触发。
这里有个经验:quorum的取值不是越大越安全,它要和存活哨兵数量配合。3个哨兵时quorum=2是最合理的,既能容忍一个哨兵故障,又能在主库真掉线时快速达成共识。
7.5 坑五:持久化策略没设计好,切换后丢数据
还有一个容易被忽略的问题:如果主库只开了RDB快照,没有开AOF,故障转移时新主很可能丢数据。因为RDB是周期性快照,最后一次快照之后写入的数据在崩溃时完全丢失。
我的建议是至少在master和slave上都开启appendonly yes,并合理设置appendfsync策略。同时关注Redis本身的主从异步复制机制:切换过程中可能有部分数据尚未同步到新主,这是一种可接受的“最终一致性”折中。如果业务对一致性要求极高,还需要在客户端层面做额外设计,比如配合WAIT命令或把Redis仅当作可丢失部分数据的缓存层来使用。
8. 从搭建完成到真正可用:还有几件小事要记牢
跑完一遍故障转移,这套环境已经能让你对哨兵机制建立起完整的直觉。但如果想把它变成可落地的生产方案,我还会建议你认真对待下面几件事。
8.1 密码认证场景下的额外配置
如果给Redis加了requirepass,主从和哨兵都要增加相应的认证配置。从库要设置masterauth,否则无法复制;哨兵配置里要增加一行sentinel auth-pass mymaster <密码>。漏掉任何一处,故障转移能正常发生,但复制链路会一直报“NOAUTH Authentication required”。
8.2 监控哨兵本身,而不是只监控Redis
哨兵是高可用系统的“大脑”,但它也是单点进程。三个哨兵节点分布在多台宿主机上才能避免物理机故障引发的全挂。另外要给哨兵的日志接入告警,这里不用太复杂,定期扫描日志中的+sdown、+odown、+switch-master关键字就够了。我经历过一次主库切换后,新主持续抖动,日志里反复出现+failover-triggered,如果没有告警,客户端会被反复断连折腾到崩溃。
8.3 客户端配置必须“带名字”连接
哨兵模式下的客户端不是简单连某个IP,而是通过masterName(比如我这里的mymaster)来获取真正的master地址。各种语言的Redis客户端基本都支持Sentinel模式,配置时务必确认选对模式,而不是只把哨兵地址当普通Redis地址来连,否则你用哨兵端口连进去只会看到一堆奇怪的命令异常。
8.4 多次演练之后再上生产
最后分享一点个人体会:不要第一次搭完哨兵就直接搬到生产环境。我在本地环境反复演练过十几次故障转移,才慢慢摸清主库恢复后的归队行为、网络闪断对sdown误判的影响、以及parallel-syncs设置过大对从库全量同步的冲击。每次演练都用真实的业务流量模拟写入,观察切换耗时和数据一致性,把参数调到一个自己心里踏实的范围,再谈上线。高可用系统不是“搭出来”就完了,而是“养出来”的。你越熟悉它在异常情况下的一举一动,真正出事时才越不慌。