Redis 主从加哨兵这套组合,放在今天依然是我见过最"性价比"的高可用方案之一。单机 Redis 扛不住读压力、挂了没人管,这是很多中小项目在流量爬坡期最先撞上的墙。主从复制解决的是"读扩展 + 数据冗余",哨兵解决的是"主节点挂了谁来顶"——两件事拼在一起,才算把"高可用"这三个字落到实处。这篇内容我打算把整套搭建过程从头到尾捋一遍,包括我实际部署时踩过的坑、参数为什么这么配、故障切换到底是怎么触发的。适合手上有一两台 Linux 机器、想给 Redis 加一层保险的开发者,也适合刚接触 Redis 高可用、看官方文档看得云里雾里的朋友。整套流程我会用三台机器来演示(一主两从 + 三个哨兵),命令、配置文件、验证步骤全部给全,照着抄能跑通。
1. 先把架构想清楚:为什么是三台而不是两台
动手之前有个问题必须先回答:主从哨兵到底需要几台机器?我见过不少人图省事,一台机器上开三个 Redis 进程、三个哨兵进程,跑是能跑起来,但这就跟把鸡蛋全放一个篮子里还上了把锁一样——机器一宕全完。哨兵的核心价值是"多数派投票",投票的前提是哨兵本身得分散在不同故障域里,否则投票毫无意义。
1.1 主从复制和哨兵各自负责什么
先把职责分清楚,后面配置才不会乱。主从复制负责的是数据层面:主节点(master)接收写请求,把写命令通过复制流同步给从节点(slave/replica),从节点对外提供读服务。它的本质是"一写多读 + 数据备份",主节点挂了从节点数据还在,但不会自动顶上来——这是关键,很多人以为配了主从就有高可用了,其实只是有了冗余。
哨兵负责的是控制层面:它是一组独立的进程,不存数据,只干三件事——监控、通知、故障转移。监控是指定期 ping 主从节点看活没活;通知是指发现异常时通过发布订阅告诉其他哨兵和客户端;故障转移是指确认主节点真的挂了之后,从从节点里选一个出来当新主,并让其余从节点改挂到新主上。这两层拼起来,才是完整的高可用闭环。
我习惯用一句话概括:主从管数据,哨兵管生死。理解了这个分工,你就明白为什么哨兵的部署数量必须是奇数、且至少三个——因为要过半投票。
1.2 三台机器的角色分配与端口规划
我这次用的规划如下,机器名用代称,实际部署时换成你自己的 IP 即可:
| 机器 | IP 代称 | Redis 角色 | Redis 端口 | 哨兵端口 |
|---|---|---|---|---|
| 节点 A | 192.168.10.11 | Master | 6379 | 26379 |
| 节点 B | 192.168.10.12 | Slave | 6379 | 26379 |
| 节点 C | 192.168.10.13 | Slave | 6379 | 26379 |
三台机器每台都跑一个 Redis 实例 + 一个哨兵实例。哨兵数量为 3,满足奇数要求,quorum(判定客观下线的票数)设为 2,这样任意一台机器挂掉,剩下两台哨兵仍能凑够多数派完成投票,系统不会失去故障转移能力。如果只部署两个哨兵,一旦挂一个就只剩一个,永远凑不够 2 票,等于白配。
提示:哨兵和 Redis 实例可以同机部署,但生产环境建议哨兵至少有一个独立于 Redis 主节点的故障域。本文为了演示方便采用同机部署,实际规划时把这点考虑进去。
端口方面,Redis 默认 6379,哨兵默认 26379,我都没改。如果你的环境里这些端口被占用,改端口的同时记得同步改哨兵配置里的sentinel monitor行和防火墙规则,否则哨兵连不上 Redis 会一直报+sdown。
2. 主从复制的落地:从配置文件到全量同步
主从复制的配置其实不复杂,核心就一行replicaof。但"配完能连上"和"配对了"是两回事,中间有几个参数和验证动作必须做到位,否则你以为同步成功了,实际可能只是连上了但没同步数据。
2.1 主节点配置:几个不能省的参数
先在三台机器上装好 Redis(版本建议 6.x 及以上,本文命令以 6.2 为准,5.x 也基本通用)。主节点 A 的redis.conf我重点改这几处:
# 绑定地址,按实际网卡改,别图省事绑 0.0.0.0 bind 192.168.10.11 # 保护模式关掉(绑定了具体 IP 后可关) protected-mode no # 端口 port 6379 # 后台运行 daemonize yes # 日志和数据目录,按实际路径改 logfile "/var/log/redis/redis.log" dir "/var/lib/redis" # 主节点密码,从节点和哨兵都要用 requirepass "YourStrongPass123" # 从节点连接主节点时用的密码(主节点上也要配,供哨兵切换后使用) masterauth "YourStrongPass123" # 开启 AOF,主从场景下比 RDB 更稳 appendonly yes这里有两个点值得展开。第一,requirepass和masterauth我建议设成一样,虽然理论上可以不同,但哨兵故障转移后新主会沿用配置,密码不一致会导致从节点连不上新主,切换直接失败。第二,appendonly yes在主从场景下我强烈建议开——RDB 是定时快照,主节点突然宕机可能丢最后一段时间的数据,AOF 的持久化粒度更细,配合主从能最大限度减少数据损失。
改完启动主节点:
redis-server /etc/redis/redis.conf redis-cli -h 192.168.10.11 -p 6379 -a YourStrongPass123 ping返回PONG说明主节点起来了。这时候用redis-cli连上去执行INFO replication,应该看到role:master且connected_slaves:0,正常,因为从节点还没配。
2.2 从节点配置:replicaof 那一行的门道
从节点 B 和 C 的配置,除了bind改成各自 IP,其余和主节点基本一致,额外加一行:
# 指定主节点,格式:replicaof <masterip> <masterport> replicaof 192.168.10.11 6379就这一行,从节点启动后会自动去连主节点、发起全量同步(PSYNC)。但这里有个新手常踩的坑:从节点默认是只读的(replica-read-only yes),这是对的,别去改。有人为了让从节点也能写,把它设成 no,结果主从数据直接冲突,同步流乱掉。从节点就该只读,写请求统一走主节点。
启动两个从节点后,回到主节点执行:
redis-cli -h 192.168.10.11 -p 6379 -a YourStrongPass123 info replication正常应该看到类似:
role:master connected_slaves:2 slave0:ip=192.168.10.12,port=6379,state=online,offset=...,lag=0 slave1:ip=192.168.10.13,port=6379,state=online,offset=...,lag=0state=online和lag=0是关键,前者说明复制链路通了,后者说明从节点跟主节点几乎无延迟。如果state是wait_bgsave或send_bulk,说明还在全量同步中,数据量大的话要等一会儿;如果一直是down,八成是密码或网络问题。
2.3 验证复制真的生效:别只看状态,要写数据
状态显示 online 不代表数据真同步了,我习惯做一次实打实的写入验证。在主节点写一个 key:
redis-cli -h 192.168.10.11 -p 6379 -a YourStrongPass123 set test:repl "hello"然后分别到两个从节点读:
redis-cli -h 192.168.10.12 -p 6379 -a YourStrongPass123 get test:repl redis-cli -h 192.168.10.13 -p 6379 -a YourStrongPass123 get test:repl两个都返回"hello",复制链路才算真正打通。这一步别省,我遇到过从节点状态显示 online 但实际因为masterauth配错、同步流被拒的情况,光看状态会误判。
注意:从节点执行
INFO replication应看到role:slave、master_link_status:up。如果master_link_status:down,优先查主节点密码和masterauth是否一致。
3. 哨兵配置:三个进程如何达成共识
主从跑通只是有了冗余,真正让系统"自愈"的是哨兵。哨兵的配置文件叫sentinel.conf,和redis.conf是两套东西,别混。三台机器上的哨兵配置内容基本一致,只有bind和sentinel myid(自动生成,不用手填)不同。
3.1 sentinel.conf 的最小可用配置
每台机器上创建/etc/redis/sentinel.conf,内容如下:
# 哨兵端口 port 26379 # 后台运行 daemonize yes # 日志 logfile "/var/log/redis/sentinel.log" # 监控主节点,格式:sentinel monitor <master-name> <ip> <port> <quorum> sentinel monitor mymaster 192.168.10.11 6379 2 # 主节点密码(Redis 6 起哨兵也要认证) sentinel auth-pass mymaster YourStrongPass123 # 判定主观下线的时间,单位毫秒,默认 30 秒 sentinel down-after-milliseconds mymaster 30000 # 故障转移时最多同时有几个从节点同步新主 sentinel parallel-syncs mymaster 1 # 故障转移超时时间 sentinel failover-timeout mymaster 180000逐行说几个关键参数。sentinel monitor里的mymaster是集群名,随便起但要全集群一致;quorum 2表示至少 2 个哨兵认为主节点挂了,才判定为客观下线(ODOWN),这是触发故障转移的门槛。down-after-milliseconds 30000是主观下线(SDOWN)的判定时间——单个哨兵 ping 主节点超过 30 秒没响应,就认为它"可能挂了",但还只是主观判断,要凑够 quorum 票才升级为客观下线。
parallel-syncs 1这个参数容易被忽略。故障转移后,新主会同时接受多个从节点同步,设成 1 表示一次只让一个从节点同步,其余排队。这样做的目的是避免新主被多个全量同步同时压垮——全量同步会 fork 子进程生成 RDB,很吃 CPU 和内存,串行化能保护新主。数据量大的集群,这个值保持 1 最稳。
3.2 哨兵启动与互相发现的机制
三台机器分别启动哨兵:
redis-sentinel /etc/redis/sentinel.conf # 或者用 redis-server 启动,效果一样 redis-server /etc/redis/sentinel.conf --sentinel启动后连上任意一个哨兵:
redis-cli -h 192.168.10.11 -p 26379 info sentinel应该看到:
sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 master0:name=mymaster,status=ok,address=192.168.10.11:6379,slaves=2,sentinels=3重点看sentinels=3和slaves=2。哨兵之间是通过主节点的发布订阅频道__sentinel__:hello互相发现的——每个哨兵启动后会往这个频道发消息,其他哨兵订阅到就知道"哦,还有同行在",然后互相记录到自己的哨兵列表里。所以哨兵不需要在配置里写彼此的地址,只要都监控同一个主节点,它们会自动组网。这个机制很多人不知道,以为要手动配哨兵列表,其实不用。
如果sentinels显示小于 3,说明哨兵之间还没互相发现,等几秒再查,或者检查三台机器的 26379 端口是否互通、密码是否一致。
3.3 用 kill 模拟主节点宕机,看切换全过程
配置完不验证等于没配。我习惯直接 kill 掉主节点进程来模拟真实宕机:
# 在主节点 A 上 redis-cli -h 192.168.10.11 -p 6379 -a YourStrongPass123 shutdown nosave然后盯着任意一个哨兵的日志:
tail -f /var/log/redis/sentinel.log你会依次看到这些事件(时间戳省略):
+sdown master mymaster 192.168.10.11 6379 # 主观下线 +odown master mymaster 192.168.10.11 6379 #quorum 2/2 # 客观下线 +try-failover master mymaster 192.168.10.11 6379 # 开始故障转移 +vote-for-leader ... # 选举领头哨兵 +elected-leader master mymaster 192.168.10.11 6379 +failover-state-select-slave master mymaster ... # 选新主 +selected-slave slave 192.168.10.12:6379 ... # 选中 B 当新主 +failover-state-send-slaveof-noone slave ... # 让 B 升主 +failover-state-wait-promotion slave ... # 等待 B 晋升 +promoted-slave slave 192.168.10.12:6379 ... # B 晋升成功 +failover-state-reconf-slaves master ... # 让 C 改挂新主 +slave-reconf-sent slave 192.168.10.13:6379 ... # 通知 C +slave-reconf-inprog slave 192.168.10.13:6379 ... +slave-reconf-done slave 192.168.10.13:6379 ... +failover-end master mymaster 192.168.10.12 6379 # 切换完成 +switch-master mymaster 192.168.10.11 6379 192.168.10.12 6379 # 主节点地址变更这一串日志就是完整的故障转移链路。核心逻辑是:先 SDOWN,凑够 quorum 变 ODOWN,然后哨兵之间选一个领头(leader)来执行转移,leader 从从节点里挑一个数据最新的(比较offset)升为主,再让其余从节点改挂新主。整个过程默认在几十秒内完成,取决于down-after-milliseconds和网络状况。
切换完成后,用redis-cli连 192.168.10.12 执行INFO replication,会看到它已经是role:master,且connected_slaves:1(C 挂过来了)。这时候原来的主节点 A 如果重启,哨兵会自动把它降级为从节点挂到新主 B 上——这个"自动降级"是哨兵很贴心的设计,不用手动干预。
4. 客户端怎么找到新主:别把 IP 写死
故障转移完成后有个现实问题:客户端原来连的是 A,现在主变成 B 了,客户端怎么知道?如果客户端配置里写死了192.168.10.11:6379,那切换对它来说等于没发生,写请求全打到已经降级的 A 上,直接报错。这是主从哨兵方案里最容易被忽略的一环。
4.1 哨兵感知模式:客户端要主动问哨兵
正确的做法是客户端连哨兵,而不是直连 Redis。以常见的 Java 客户端为例,配置里给的是哨兵地址列表:
// 伪代码示意,具体 API 按客户端版本调整 Set<String> sentinels = new HashSet<>(); sentinels.add("192.168.10.11:26379"); sentinels.add("192.168.10.12:26379"); sentinels.add("192.168.10.13:26379"); JedisSentinelPool pool = new JedisSentinelPool( "mymaster", // 集群名,和 sentinel monitor 里一致 sentinels, new JedisPoolConfig(), "YourStrongPass123" // 哨兵密码 );客户端启动时会问哨兵"mymaster 现在的主是谁",哨兵返回当前主地址,客户端再连过去。故障转移后,哨兵会通过发布订阅通知客户端"主变了",客户端自动重连新主。整个过程对业务代码透明,业务只管从连接池拿连接。
这里有个细节:哨兵地址列表要写全,别只写一个。如果只写一个哨兵地址,那个哨兵挂了客户端就找不到集群了。三个都写上,客户端会轮询,容错性最好。
4.2 密码认证的两层坑
Redis 6 之后哨兵也支持认证,这里有两层密码容易搞混:一层是客户端连哨兵的密码(如果哨兵配了requirepass),另一层是哨兵连 Redis的密码(sentinel auth-pass)。我见过有人只配了后者,客户端连哨兵时没带密码被拒,排查半天。
如果你给哨兵也加了requirepass(生产环境建议加),那客户端连接池初始化时要把哨兵密码传进去。不同客户端 API 不一样,有的叫sentinelPassword,有的直接在连接参数里带。配之前先看客户端文档,别想当然。
提示:哨兵配置里如果加了
requirepass,记得三台机器都加,且密码一致。哨兵之间通信也要认证,不一致会导致互相发现失败,sentinels数量上不去。
4.3 切换期间的写失败怎么兜底
即便客户端用了哨兵模式,故障转移那几十秒内写请求还是会失败——因为新主还没选出来,或者客户端还没收到通知。这段"脑裂窗口"是客观存在的,任何主从方案都躲不掉。我的处理经验是:业务层要有重试。
具体做法是给写操作包一层重试逻辑,捕获连接异常后等 1~2 秒重试,最多重试 3 次。因为哨兵的切换通常在 30 秒内完成(down-after-milliseconds设 30 秒的话),重试窗口覆盖得住。如果业务对写一致性要求极高,那得考虑更重的方案,但绝大多数场景下"重试 + 哨兵"已经够用。
另外提醒一句:切换期间如果有客户端还在往旧主写(旧主还没被降级),会产生数据不一致。哨兵通过min-replicas-to-write这类参数能在一定程度上缓解,但根治要靠客户端及时感知。所以客户端一定要用哨兵模式,别直连。
5. 那些文档里不写、但实际会咬人的细节
配置跑通只是开始,真正让这套集群在生产里稳住,靠的是一堆细节。下面这几条都是我在实际部署里踩出来的,官方文档要么没提,要么一笔带过。
5.1 时间同步:哨兵误判的隐形杀手
哨兵的down-after-milliseconds是基于本地时间算的,如果三台机器时间不同步,可能出现一台哨兵认为"已经过了 30 秒",另一台认为"才过了 10 秒",导致 SDOWN 判定不一致,故障转移被拖延甚至误触发。部署前务必确认三台机器开了时间同步,这是最容易被忽略、又最影响稳定性的基础项。
检查方法很简单,三台机器分别执行date,看时间差是否在秒级以内。如果差得多,先把时间同步配好再谈别的。我见过一次线上切换慢,排查半天最后发现是某台机器时间漂了十几分钟,哨兵日志时间戳全乱,定位极其痛苦。
5.2 内存与 maxmemory:别让主节点被 OOM 拖死
主从架构下,主节点内存压力最大,因为它要处理所有写请求还要维护复制缓冲区。如果没设maxmemory,主节点内存涨到系统上限会被 OOM killer 干掉,触发一次本可避免的故障转移。我的建议是给主节点设maxmemory,并配好淘汰策略:
maxmemory 4gb maxmemory-policy allkeys-lruallkeys-lru适合纯缓存场景;如果数据不能随便淘汰,用noeviction并配合监控告警,让运维提前介入。另外,复制缓冲区(repl-backlog-size)默认 1MB,写流量大的集群建议调到 64MB 以上,否则从节点短暂断连后可能触发全量同步,很吃资源。
5.3 哨兵数量与 quorum 的搭配陷阱
前面说哨兵要奇数、至少三个,但 quorum 怎么设也有讲究。quorum 是判定客观下线需要的票数,它和哨兵总数是两个概念。三个哨兵时 quorum 设 2 是合理的;但如果哨兵扩到 5 个,quorum 还是 2,那只要 2 个哨兵认为主挂了就触发转移,容错性反而下降(可能因为网络抖动误判)。一般原则是quorum 设为哨兵总数的一半加一,3 个设 2,5 个设 3,这样既保证能触发,又不会太敏感。
还有个隐藏规则:故障转移要真正执行,除了满足 quorum,还需要多数派哨兵存活(majority)。3 个哨兵时多数派是 2,所以挂 1 个还能切换;如果挂 2 个只剩 1 个,即便这 1 个认为主挂了,也凑不够多数派,切换不会发生。这就是为什么哨兵数量必须是奇数——偶数在平票时无法形成多数派。
5.4 日志和监控:切换发生了你得知道
哨兵的日志默认只记关键事件,但生产环境我建议把日志级别调到notice甚至debug(排查期),并接入日志收集。更重要的是监控+switch-master事件——这个事件一出现就说明发生了主从切换,必须有人知道。可以写个简单的脚本订阅哨兵的发布订阅频道:
redis-cli -h 192.168.10.11 -p 26379 -a YourStrongPass123 \ psubscribe "+switch-master" "+sdown" "+odown"一旦有切换,终端会实时打印出来。生产环境把这个接到告警系统,切换即告警,别等业务报错了才发现主早就换了。
6. 收尾前再聊两句实操体会
整套搭下来,主从哨兵的门槛其实不高,配置文件加起来不到五十行,难的是把每个参数背后的"为什么"想明白。我最初配的时候也是照抄教程,结果第一次真实故障时切换没成功,翻日志才发现是masterauth和requirepass不一致,从节点连不上新主。从那以后我养成了一个习惯:每次配完,一定手动 kill 一次主节点,完整走一遍切换流程,把日志从头看到尾,确认每个事件都符合预期,才算这套集群真的可用。
还有个心得是关于"够用就好"。主从哨兵不是银弹,它解决的是单点故障和读扩展,但写能力还是单主,数据量特别大或者写 QPS 极高时,得上 Cluster 分片方案。选型时先想清楚自己的瓶颈在哪——是读扛不住、还是怕主挂了、还是写也扛不住,对症下药比盲目上复杂方案强。对大多数中小项目来说,一主两从加三哨兵,配合客户端哨兵模式和业务重试,已经能覆盖 90% 的高可用需求了。