外观
哨兵 Sentinel:三节点自动故障转移
主从解决了"读扩展"和"热备",但 master 挂了没有任何东西会把从库提上来。哨兵就是那个东西。
场景:什么时候用哨兵
- 单 master 写、多 replica 读,需要自动切主。
- 能接受秒级到几十秒的不可用(切换期间写会失败)。
- 数据量在单机内存范围内(超过就得看 Cluster)。
如果单实例内存放不下数据、或者写入 QPS 单机扛不住,哨兵解决不了,直接看 Cluster。
一、先明确哨兵的三个能力
| 能力 | 说明 |
|---|---|
| 监控 | 定期 PING master/replica/其他 sentinel |
| 通知 | 故障时通过 notify-script 调外部脚本(发告警) |
| 自动故障转移 | 选一个从库提为主,其余从库改挂新主,通知客户端 |
哨兵不存数据,它是一组独立进程(默认端口 26379),通常和 Redis 混部在同一批机器上。
二、拓扑与节点数
推荐 3 个哨兵,至少 3 个,且必须奇数。
| 机器 | Redis | Sentinel |
|---|---|---|
| 10.0.0.11 | master :6379 | :26379 |
| 10.0.0.12 | replica :6379 | :26379 |
| 10.0.0.13 | replica :6379 | :26379 |
为什么必须 ≥3:
quorum判定客观下线(ODOWN)需要票数,2 个哨兵时一台宕机就无法达成多数。- 选 leader 时要求 多数派(majority = N/2 + 1)。2 个哨兵里死 1 个,剩下的 1 个拿不到 2 票,永远选不出 leader。
- 3 个哨兵可以容忍 1 台故障;5 个容忍 2 台。
哨兵和 Redis 混部不是高可用
3 台机器各跑 1 Redis + 1 Sentinel,机器全挂就丢 1 个 Sentinel 和 1 个 Redis,还能工作。但如果你把 3 个 Sentinel 全放在同一台机器上,那台机器一挂就彻底失去切换能力。按机器分散,不是按进程数凑数。
三、配置
三台机器上的 sentinel.conf 只有 sentinel monitor 那行不同(各自用本机视角写同一个 master)。
ini
# /etc/redis/sentinel.conf
port 26379
bind 0.0.0.0
protected-mode no
daemonize no
logfile /var/log/redis/sentinel.log
dir /var/lib/redis/sentinel
# 关键:monitor <名字> <master ip> <port> <quorum>
sentinel monitor mymaster 10.0.0.11 6379 2
# 判定主观下线的时间
sentinel down-after-milliseconds mymaster 5000
# 故障转移超时
sentinel failover-timeout mymaster 60000
# 一次切主后,允许几个从库并行同步
sentinel parallel-syncs mymaster 1
# master / replica 都有密码时
sentinel auth-pass mymaster YourStrongPass
# 跨 NAT / 容器时显式声明自己的地址
# sentinel announce-ip 10.0.0.11
# sentinel announce-port 26379
# 切换成功后执行的脚本(可选,做告警)
# sentinel notification-script mymaster /usr/local/bin/redis-failover-notify.sh
# sentinel client-reconfig-script mymaster /usr/local/bin/redis-client-reconfig.sh四个参数详解:
| 参数 | 作用 | 调错的后果 |
|---|---|---|
quorum 2 | 至少 2 个哨兵认为 master 挂了 → 标记 ODOWN | 设太大(超过哨兵数)永远判不出来 |
down-after-milliseconds 5000 | 5 秒无响应算主观下线 | 设太小:一次 GC/网络抖动就误切 |
failover-timeout 60000 | 一次切换的总预算 | 设太小:切换没完成就重来,反复切 |
parallel-syncs 1 | 切完后从库逐个同步新主 | 设太大:新主被 N 个全量同步打满 |
quorum 和 majority 不是一回事
quorum 只决定"要不要判 ODOWN"。真正选 leader 用的是 majority:3 个哨兵需要 2 票,5 个需要 3 票。
所以 quorum=1 + 3 个哨兵也能工作(1 个认为挂了就 ODOWN,但仍需 2 票才能选出 leader)。反过来 quorum=3 + 2 个哨兵是死配置:永远达不到 3 票,永远不切。
四、systemd 托管
ini
# /etc/systemd/system/redis-sentinel.service
[Unit]
Description=Redis Sentinel
After=network-online.target redis-6379.service
Wants=network-online.target
[Service]
Type=simple
User=redis
Group=redis
ExecStartPre=/bin/mkdir -p /var/lib/redis/sentinel
ExecStartPre=/bin/chown -R redis:redis /var/lib/redis/sentinel /var/log/redis
ExecStart=/usr/local/redis/bin/redis-server /etc/redis/sentinel.conf --sentinel
Restart=always
RestartSec=3
LimitNOFILE=65535
[Install]
WantedBy=multi-user.targetbash
systemctl daemon-reload
systemctl enable --now redis-sentinel
systemctl status redis-sentinel --no-pager输出:
text
● redis-sentinel.service - Redis Sentinel
Loaded: loaded (/etc/systemd/system/redis-sentinel.service; enabled)
Active: active (running) since Tue 2026-10-27 17:30:11 CST; 4s ago
Main PID: 23110 (redis-server)
Status: "Sentinel is ready"
Tasks: 5 (limit: 100000)
Memory: 8.4M哨兵日志:
text
+monitor master mymaster 10.0.0.11 6379 quorum 2
* +slave slave 10.0.0.12:6379 10.0.0.12 6379 @ mymaster 10.0.0.11 6379
* +slave slave 10.0.0.13:6379 10.0.0.13 6379 @ mymaster 10.0.0.11 6379
+sentinel sentinel 4f1a... 10.0.0.12 26379 @ mymaster 10.0.0.11 6379
+sentinel sentinel 9c2e... 10.0.0.13 26379 @ mymaster 10.0.0.11 6379解释:+monitor 是自己开始监控;+slave 是发现从库;+sentinel 是通过 master 的 __sentinel__:hello 频道自动发现的其他哨兵。这一步是自动的,不需要在配置文件里互相列出。
五、验证
bash
redis-cli -p 26379 sentinel get-master-addr-by-name mymaster
redis-cli -p 26379 sentinel master mymaster
redis-cli -p 26379 sentinel replicas mymaster
redis-cli -p 26379 sentinel sentinels mymaster
redis-cli -p 26379 sentinel ckquorum mymaster输出:
text
1) "10.0.0.11"
2) "6379"
1) "name"
2) "mymaster"
3) "ip"
4) "10.0.0.11"
5) "port"
6) "6379"
7) "runid"
8) "3d41b7c0e5a29f6b8c1d0e2f4a6b8c9d0e1f2a3b"
9) "flags"
10) "master"
11) "quorum"
12) "2"
13) "num-slaves"
14) "2"
15) "num-other-sentinels"
16) "2"
OK 3 usable Sentinels. Quorum and failover authorization can be reachedOK 3 usable Sentinels 是上线前必须看到的一行——它表示多数派可达,真出事时能选出 leader。
六、手工演练一次切换
不要等到真出事才第一次看切换日志。
bash
redis-cli -p 26379 sentinel failover mymastertext
OK然后跟日志:
bash
tail -f /var/log/redis/sentinel.logtext
+sdown master mymaster 10.0.0.11 6379
+odown master mymaster 10.0.0.11 6379 #quorum 2/2
+try-failover master mymaster 10.0.0.11 6379
+vote-for-leader 4f1a2b... 1
+elected-leader master mymaster 10.0.0.11 6379
+failover-state-select-slave master mymaster 10.0.0.11 6379
+selected-slave slave 10.0.0.12:6379 10.0.0.12 6379 @ mymaster 10.0.0.11 6379
+failover-state-send-slaveof-noone slave 10.0.0.12:6379 10.0.0.12 6379 @ mymaster 10.0.0.11 6379
+failover-state-wait-promotion slave 10.0.0.12:6379 10.0.0.12 6379 @ mymaster 10.0.0.11 6379
+promoted-slave slave 10.0.0.12:6379 10.0.0.12 6379 @ mymaster 10.0.0.11 6379
+failover-state-reconf-slaves master mymaster 10.0.0.11 6379
+slave-reconf-sent slave 10.0.0.13:6379 10.0.0.13 6379 @ mymaster 10.0.0.11 6379
+slave-reconf-inprog slave 10.0.0.13:6379 10.0.0.13 6379 @ mymaster 10.0.0.11 6379
+slave-reconf-done slave 10.0.0.13:6379 10.0.0.13 6379 @ mymaster 10.0.0.11 6379
+switch-master mymaster 10.0.0.11 6379 10.0.0.12 6379按顺序读这段日志:
+sdown:某个哨兵认为 master 主观下线。+odown #quorum 2/2:达到 quorum,客观下线。+try-failover→+vote-for-leader→+elected-leader:竞选 leader。+selected-slave:按优先级、offset、runid 选出新主。+promoted-slave:replicaof no one完成提升。+slave-reconf-*:其他从库改挂新主。+switch-master:切换对外生效的这一刻。
选新主的排序规则(依次比较):
replica-priority小的优先(0 表示永不参选)。- 复制 offset 大的优先(数据更新)。
runid字典序小的优先(纯粹为了确定)。
七、旧 master 回来了会怎样
哨兵会把它降级为从库,挂到新主下面:
text
+convert-to-slave slave 10.0.0.11:6379 10.0.0.11 6379 @ mymaster 10.0.0.12 6379但它的数据是旧的(故障期间的写它没有)。全量同步后,它会和新主一致——也就是故障期间的写在它身上被抹掉,这部分数据本来就在新主上。
注意:sentinel 会自动改写 sentinel.conf,加入 sentinel known-replica、sentinel current-epoch 等行。所以:
不要手改运行中的 sentinel.conf 然后直接重启
哨兵的配置文件是它自己的状态存储。手改会被覆盖,或者和一个过期的 epoch 冲突导致"以为自己是 leader"。要改配置应:
redis-cli -p 26379 sentinel flushconfig让哨兵写回当前状态,或- 先
systemctl stop,改完再启动,并确认sentinel ckquorum正常。
八、客户端怎么接
客户端不能只连 master IP,否则切主后就断了。必须通过哨兵问"现在谁是 master"。
Java 侧配置(细节见 Java 客户端):
yaml
spring:
data:
redis:
password: YourStrongPass
sentinel:
master: mymaster
nodes: 10.0.0.11:26379,10.0.0.12:26379,10.0.0.13:26379命令行验证哨兵返回的是新主:
bash
redis-cli -p 26379 sentinel get-master-addr-by-name mymastertext
1) "10.0.0.12"
2) "6379"九、踩过的坑
| 现象 | 根因 | 处置 |
|---|---|---|
ckquorum 报 NOQUORUM | 哨兵数不足或互相发现失败 | 查 +sentinel 日志、26379 是否互通 |
| 反复切换 | down-after-milliseconds 太小,网络抖动误判 | 调到 5000~10000 |
| 切了但客户端还连旧主 | 客户端写死了 master IP | 改用哨兵配置 |
| 切换后从库全部全量同步,新主被打满 | parallel-syncs 太大 | 设为 1~2 |
| 两个哨兵都自称 leader | 网络分区 + 手改 epoch | sentinel reset mymaster 重置状态 |
| docker 里哨兵报告错误 IP | 容器内网 IP 被写进配置 | sentinel announce-ip 指定宿主机 IP |
哨兵起来了但 num-other-sentinels:0 | 密码不对,__sentinel__:hello 订阅失败 | 配 sentinel auth-pass |
| 明明 master 活着却被判 ODOWN | master 阻塞(大 key DEL、SAVE)超过阈值 | 查慢日志,改同步删除为 UNLINK |