Redis 从 0 到 1(九):主库宕机 30 秒自动切主——1 主 2 从 3 哨兵 + Go 客户端

我不会起名字322 · 后端 / 算法 / 数据库

📘 技术栈 | 🧩 力扣 Hot100 | 🐹 Go 项目 | 🟥 Redis | 🐬 MySQL

文章目录

  • [Redis 从 0 到 1(九):主库宕机 30 秒自动切主------1 主 2 从 3 哨兵 + Go 客户端](#Redis 从 0 到 1(九):主库宕机 30 秒自动切主——1 主 2 从 3 哨兵 + Go 客户端)
    • [一、现象:凌晨 3 点主库挂了,值班同学手忙脚乱](#一、现象:凌晨 3 点主库挂了,值班同学手忙脚乱)
    • [二、docker-compose 搭建 1 主 2 从 3 哨兵(收藏点 1)](#二、docker-compose 搭建 1 主 2 从 3 哨兵(收藏点 1))
    • [三、故障转移全流程(30 秒里发生了什么)](#三、故障转移全流程(30 秒里发生了什么))
    • [四、哨兵 5 个核心参数(收藏点 2)](#四、哨兵 5 个核心参数(收藏点 2))
    • [五、Go 实操:哨兵客户端(收藏点 3)](#五、Go 实操:哨兵客户端(收藏点 3))
    • 六、部署与排查清单
    • 七、总结

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 步:

  1. ckquorum 通过(证明没有哨兵网络孤岛);
  2. 杀掉一个哨兵,告警与切换仍正常;
  3. 杀掉主库,计时从 ODOWN 到新主可写;
  4. 旧主复活,确认它被自动降级为从库、没有"两个主"脑裂残留(第 31 篇详谈)。

七、总结

  1. 哨兵 = 监控 + 通知 + 自动故障转移,3 个哨兵 quorum=2 是标准配置;
  2. 切主默认 20~40 秒,"无感"靠客户端重试吃下切换窗口;
  3. down-after-milliseconds 在"快速切换"和"避免惊群"之间取舍;
  4. Go 侧用 FailoverClient,让"查号"自动化。

下一篇预告:哨兵解决了高可用,但单实例的内存和吞吐天花板还在------10 万 QPS、几百 GB 数据怎么扩?下一篇《16384 个槽怎么分?6 节点 Cluster 与 Go 重定向处理》,我们进入分片时代。

相关推荐
姜鱼问生4 小时前
每次开机手动检查 6 样东西?我写了个 2 秒自检脚本
运维·网络·nginx·docker·容器
wangqiaowq4 小时前
PostgresSQL server Docker 安装部署
运维·docker·容器
程序员阿明4 小时前
docker搭建视频流服务go2rtc,webRTC
docker·容器·webrtc
TTATTC5 小时前
让 16 个 AI 员工在飞书群自我协同:基于 OpenClaw 的多 Agent 敏捷 Squad 拓扑与一人公司实操
docker·开源
TTATTC5 小时前
从分片续传到 SPI 插件化扩展:Free-FS 企业级文件系统核心架构与万能预览技术拆解
docker·开源
JavaPub-rodert6 小时前
Docker 容器为什么 stop 半天停不下来?讲透 PID 1、SIGTERM、SIGKILL、僵尸进程与 tini
运维·docker·容器
毕业设计7039 小时前
(免费领源码)SSM董家湾线上点餐系统04682- java、PHP、python、C#、小程序、大数据、单片机、网络工程等)
java·redis·mysql·tomcat·maven·mybatis·ssm
EterNity_TiMe_10 小时前
Docker服务越装越多记不住端口?我用Fenrus把NAS里的常用入口整理成一个导航页
运维·人工智能·docker·ai·容器·cpolar
吴声子夜歌10 小时前
Docker入门与实战——libnetwork插件化网络功能
网络·docker·容器