我不会起名字322 · 后端 / 算法 / 数据库
文章目录
- [Redis 从 0 到 1(九):主库宕机 30 秒自动切主------1 主 2 从 3 哨兵 + Go 客户端](#Redis 从 0 到 1(九):主库宕机 30 秒自动切主——1 主 2 从 3 哨兵 + Go 客户端)
Redis 从 0 到 1(九):主库宕机 30 秒自动切主------1 主 2 从 3 哨兵 + Go 客户端
上一篇《Redis 从 0 到 1(八):读写分离读到旧值------主从延迟 4 来源与 Go 探测器》我们量化了主从复制延迟,链接:https://blog.csdn.net/2501_92769340/article/details/167399552 。
本篇解决下一个问题:主库宕机谁来切主?------答案是哨兵(Sentinel)。
一、现象:凌晨 3 点主库挂了,值班同学手忙脚乱
人工切主的典型现场:告警 → 登录服务器 → REPLICAOF NO ONE 把从库升主 → 其他从库指向新主 → 改配置、重启应用......顺利的话十几分钟,中间业务要么报错要么读旧数据。
哨兵做三件事把这个流程压到 30 秒内:监控 (持续探测主从健康)、通知 (故障时发事件)、自动故障转移(选一个新主、让其他从库重新跟随、广播新主地址)。本篇用一份 docker-compose 把它完整跑起来。
二、docker-compose 搭建 1 主 2 从 3 哨兵(收藏点 1)
yaml
services:
redis-master:
image: redis:7.2-alpine
command: redis-server --appendonly yes
ports: ["6379:6379"]
redis-replica1:
image: redis:7.2-alpine
command: redis-server --appendonly yes --replicaof redis-master 6379
redis-replica2:
image: redis:7.2-alpine
command: redis-server --appendonly yes --replicaof redis-master 6379
sentinel1:
image: redis:7.2-alpine
command: redis-server /etc/sentinel.conf --sentinel
volumes: ["./sentinel1.conf:/etc/sentinel.conf"]
sentinel2:
image: redis:7.2-alpine
command: redis-server /etc/sentinel.conf --sentinel
volumes: ["./sentinel2.conf:/etc/sentinel.conf"]
sentinel3:
image: redis:7.2-alpine
command: redis-server /etc/sentinel.conf --sentinel
volumes: ["./sentinel3.conf:/etc/sentinel.conf"]
sentinel.conf 三份内容相同(换端口即可):
conf
port 26379
sentinel monitor mymaster redis-master 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
bash
docker compose up -d
# 验证哨兵视角
docker exec sentinel1 redis-cli -p 26379 SENTINEL master mymaster
docker exec sentinel1 redis-cli -p 26379 SENTINEL replicas mymaster
monitor ... 2 的 2 是法定人数(quorum):至少 2 个哨兵都认为主库挂了,才判定客观下线。3 个哨兵配 2 是标准搭配(容忍 1 个哨兵自己脑抽)。
三、故障转移全流程(30 秒里发生了什么)
bash
docker kill redis-master # 模拟主库宕机
docker exec sentinel1 redis-cli -p 26379 SENTINEL master mymaster
# 观察 Info 里的 flags: odown -> 等待几秒 -> 新主的 IP 出现在输出里
时间线(默认参数下约 20~40 秒):
| 阶段 | 耗时 | 动作 |
|---|---|---|
| SDOWN | 5s | 单个哨兵连续 5 秒 PING 不通 → 主观下线 |
| ODOWN | +0s | 达到 quorum 个哨兵 SDOWN → 客观下线,准备选举 |
| 哨兵选举 leader | 数秒 | 哨兵们互相投票选出一个来执行切换 |
| 选新主 | 秒级 | 从在线从库中挑优先级高(replica-priority 小)、复制偏移量最大、runid 最小的 |
| 切换 | 秒级 | 对新主 REPLICAOF NO ONE;其他从库 REPLICAOF 新主;旧主复活后被降级为新主的从库 |
四、哨兵 5 个核心参数(收藏点 2)
| 参数 | 默认 | 含义与调优 |
|---|---|---|
down-after-milliseconds |
30000 | 多久 PING 不通算主观下线。5 秒意味着网络抖动 >5s 就会触发一次不必要切换,太小会"惊群切换";常用 5~10s |
quorum(monitor 末位) |
--- | 判定客观下线需要的哨兵数。3 哨兵配 2 |
failover-timeout |
180000 | 一次切换的超时与两次切换的最小间隔;同一次故障期间不会反复切 |
parallel-syncs |
1 | 切换时几个从库同时向新主全量同步。设大会加速但会打爆网络 |
replica-priority(从库侧) |
100 | 选主优先级,数字小的优先;不想被选中设为 0 |
五、Go 实操:哨兵客户端(收藏点 3)
应用侧不需要自己连三个哨兵做发现------go-redis 的 FailoverClient 会:
go
package main
import (
"context"
"fmt"
"time"
"github.com/redis/go-redis/v9"
)
func main() {
rdb := redis.NewFailoverClient(&redis.FailoverOptions{
MasterName: "mymaster",
SentinelAddrs: []string{"127.0.0.1:26379", "127.0.0.1:26380", "127.0.0.1:26381"},
Password: "redis123",
DialTimeout: 3 * time.Second,
ReadTimeout: 2 * time.Second,
WriteTimeout: 2 * time.Second,
PoolSize: 20,
MaxRetries: 3, // 切换窗口内的命令由重试兜底
})
defer rdb.Close()
ctx := context.Background()
for i := 0; i < 10; i++ {
if err := rdb.Set(ctx, "sentinel:probe", i, time.Minute).Err(); err != nil {
fmt.Printf("[%d] 写入失败(切主窗口内属正常): %v\n", i, err)
} else {
v, _ := rdb.Get(ctx, "sentinel:probe").Result()
fmt.Printf("[%d] 当前主库读回: %v\n", i, v)
}
time.Sleep(time.Second)
}
}
测试:运行程序的同时 docker kill redis-master,观察输出------中间 1~3 次写入失败(No route to host / connection refused),随后自动恢复。这就是"无感"的真相:有几秒窗口,靠客户端重试吃掉。要想零失败,业务层需要读旧值容忍或本地队列缓冲(第 31 篇脑裂还会再谈切换期数据)。
FailoverClient 与第 2 篇普通 Client 的区别:启动时向哨兵问"现在谁是主",拿到主地址后直连;主变更时会重新询问。它不连哨兵转发流量,哨兵只当"查号台"。
六、部署与排查清单
| 命令 | 作用 |
|---|---|
SENTINEL master mymaster |
查看主库状态、当前地址、SDOWN/ODOWN 标记 |
SENTINEL replicas mymaster |
查看从库列表与偏移量 |
SENTINEL sentinels mymaster |
查看其他哨兵(确认两两可达,防止"半数分区"误判) |
SENTINEL ckquorum mymaster |
检查当前可达法定人数(部署后必跑) |
INFO sentinel(哨兵上) |
哨兵自己的视角 |
部署检查 4 步:
ckquorum通过(证明没有哨兵网络孤岛);- 杀掉一个哨兵,告警与切换仍正常;
- 杀掉主库,计时从 ODOWN 到新主可写;
- 旧主复活,确认它被自动降级为从库、没有"两个主"脑裂残留(第 31 篇详谈)。
七、总结
- 哨兵 = 监控 + 通知 + 自动故障转移,3 个哨兵 quorum=2 是标准配置;
- 切主默认 20~40 秒,"无感"靠客户端重试吃下切换窗口;
- down-after-milliseconds 在"快速切换"和"避免惊群"之间取舍;
- Go 侧用 FailoverClient,让"查号"自动化。
下一篇预告:哨兵解决了高可用,但单实例的内存和吞吐天花板还在------10 万 QPS、几百 GB 数据怎么扩?下一篇《16384 个槽怎么分?6 节点 Cluster 与 Go 重定向处理》,我们进入分片时代。