---
tags: [Redis, 缓存, 高可用, 主从, Sentinel]
create time: 2026-05-15 18:13
---
# Redis 主从复制与 Sentinel
## 概述
### 从单机困境说起
假设你的 Redis 跑在一台服务器上,所有读写都走这一个实例。随着业务增长,两个现实问题会逐渐暴露:
1. **读性能瓶颈**:热点 key 被反复访问,单实例 QPS 顶到天花板(通常 10w 左右),请求开始排队
2. **单点故障风险**:服务器宕机 = Redis 全线不可用,数据甚至可能丢失
> [!QUESTION] 想一想:如果让你来设计解决方案,你会怎么做?
> - 瓶颈在"读"→ 能不能让**多个节点分摊读请求**?
> - 风险在"单点"→ 能不能让数据**自动备份到其他机器**?
> - 故障了靠人恢复太慢 → 能不能**自动检测并切换**?
这就是 **主从复制** 和 **Sentinel** 要解决的问题。
### 两大核心机制
| 机制 | 解决什么问题 | 一句话说明 |
|------|------------|----------|
| **主从复制(Replication)** | 数据冗余 + 读扩展 | Master 处理写操作,Replica 自动同步数据并承担读请求 |
| **Sentinel(哨兵)** | 人工恢复慢 | 自动检测 Master 故障,提升最佳 Replica 为新 Master |
通俗地说:主从复制是"抄作业"机制——Master 做完(写入),Replica 抄一份;Sentinel 是"监考老师"——发现 Master"交不了卷"(宕机),立刻指定一个 Replica"接替答题"。
> [!SUMMARY] 知识定位
> - ✅ 本方案适合 **读多写少** 的场景(8:2 ~ 9:1)
> - ⚠️ 写入仍然集中在单点,若需水平写入 → [[hhs/Redis/07-集群方案]]
> - 🔧 这是学习 Redis Cluster 的前置知识
```mermaid
flowchart TD
Master["Master
写操作入口"]
R1["Replica-1"]
R2["Replica-2"]
R3["Replica-3"]
Master -->|"全量/增量同步"| R1
Master -->|"全量/增量同步"| R2
Master -->|"全量/增量同步"| R3
ClientRW["写请求"] --> Master
ClientR["读请求"] --> R1
ClientR --> R2
ClientR --> R3
```
## 一、主从复制原理
### 异步 vs 半同步
复制的核心矛盾是:**性能** 和 **数据安全** 不可兼得。我们先看两种策略的区别:
```mermaid
flowchart LR
subgraph async["异步复制(Redis 默认)"]
A1["Master 写入"] -->|"立即返回 OK"| A2["客户端"]
A1 -.->|"后台异步发送"| A3["Replica"]
end
subgraph semisync["半同步(手动启用)"]
B1["Master 写入"] -->|"等待 Replica 确认"| B2["Replica"]
B2 -->|"确认收到"| B3["客户端收到 OK"]
end
```
> [!QUESTION] 为什么 Redis 默认选择异步复制?
> 想象一下:你去超市买东西,收银员每收一笔钱都要先打电话给总部确认到账了才给你小票——你愿意等吗?对追求低延迟的 Redis 来说,**等 Replica 确认 = 强制每次写入多等一次网络往返**(通常 0.1~1ms),这对微秒级响应的场景是不可接受的。
**异步复制** 的工作方式:Master 写完就返回客户端,Replica 的事"后台慢慢同步"。代价是极端情况下可能丢数据:
```text
场景: 写 A=1 → Master 返回 OK → Master 突然宕机(数据还没来得及传到 Replica)
→ Replica 上没有 A=1 → 用 Replica 接管后,这个写入就丢了
```
> [!NOTE] 这个丢数据的概率高吗?
> 正常情况下极低——因为 Master 到 Replica 的同步几乎是实时的(毫秒级),只有在 Master 写入后**立刻宕机**的极短窗口内才会丢。这就像你刚存完钱、银行还没来得及记账就停电了——概率很小,但在金融场景下不能接受。
#### 如果业务要求"至少一份副本确认"呢?
Redis 提供了 `WAIT` 命令,可以让客户端**阻塞等待**指定数量的副本确认同步完成。这不算严格的半同步(因为 Master 已经先写入并返回了),但能大幅降低丢数据的概率:
```go
// Go: 写入后,确保至少 1 个副本已经同步了这条命令
client.Set(ctx, "order:12345", orderData, 0)
numReplicas := 1 // 至少 1 个 replica 收到
timeout := 100 // 最多等 100ms,超时就放弃
replicaCount := client.Do(ctx, "WAIT", numReplicas, timeout).Int()
if replicaCount >= 1 {
// 放心:就算 Master 立刻宕机,至少有 1 个 Replica 有这份数据
} else {
// 超时了,数据可能只在 Master 上 → 业务层面考虑重试或告警
}
```
> [!TIP] WAIT 的代价
> `WAIT` 会阻塞当前连接直到满足条件或超时。高频写场景慎用,建议仅在关键业务路径上使用(如订单创建、扣款等),普通缓存写入不需要。
### 全量同步 vs 增量同步
主从同步分为两种模式,理解它们的前提是知道一个关键概念:**Replication Offset(复制偏移量)**——你可以把它想象成一条数据"流水线"上的**刻度尺**。Master 每写入一条命令,刻度就往后移一点;Replica 同步到哪里了,也有自己的刻度。只要两个刻度对得上,就能"增量同步"。
> [!NOTE] PSync:一次连接,多次复用
> Redis 2.8+ 引入 PSYNC 协议替代旧版 SYNC,核心优势:**断线重连后可增量同步**。旧版 SYNC 每次断线都得全量传输(相当于每次请假都得抄一整本笔记),PSYNC 改为"只抄你缺的那几页"。
```mermaid
sequenceDiagram
participant R as Replica
participant M as Master
Note over R,M: "阶段 1: 全量同步(首次连接或断线过久)"
R->>M: PSYNC ? -1 (首次连接, 没有 offset 记录)
M->>M: 触发 BGSAVE, 生成 RDB 快照文件
M-->>R: +FULLRESYNC , 然后发送 RDB 文件
R->>R: 清空旧数据, 用 RDB 全量覆盖
Note over M: "RDB 生成期间, 新的写入命令不能丢"
loop "RDB 传输期间的新写入"
M->>M: 追加到 repl-backlog-buffer
end
M-->>R: 发送 backlog 中缓存的命令流
R->>R: 重放缓冲命令, 追上 Master 的进度
Note over R,M: "阶段 2: 增量同步(后续心跳, 每秒一次)"
loop "正常运行期间"
R->>M: PSYNC
M->>R: 只发送 offset 之后的增量命令
end
```
> [!QUESTION] 全量同步的代价是什么?
> 全量同步需要 Master 执行 `BGSAVE`(fork 子进程生成 RDB 文件),然后通过网络传给 Replica。如果数据量大(比如 10GB),这个过程会**消耗大量 CPU、内存和网络带宽**。所以我们的目标是:**尽量避免全量同步,让增量同步成为常态**。
#### 什么情况下触发全量同步?
| 触发条件 | 为什么? | 能避免吗? |
|---------|------|------|
| **首次连接** | Replica 是空的,没有数据也没有 offset,只能全量下载 | ❌ 必然发生 |
| **Master 重启** | Master 重启后 Run ID 变了,Replica 认不出"老朋友" | ⚠️ 持久化可缓解(重启后 Run ID 不变) |
| **Replica 手动执行 SLAVEOF** | 相当于主动"认新主人",必须重新来过 | ❌ 手动触发,通常可控 |
| **Backlog 溢出** | 断线太久,Master 的"缓冲笔记本"已经翻页覆盖了断线前的内容 | ✅ **增大 `repl-backlog-size`** |
| **config-resetstat 执行** | 元数据被重置,offset 信息丢失 | ⚠️ 少用此命令 |
> [!WARNING] 生产环境最常见的"不必要全量同步"
> 绝大多数情况是 **Backlog 过小**导致的。网络抖动几秒,Backlog 不够用,Replica 被迫全量同步——这在网络不稳定的环境中会反复发生,严重拖垮 Master 性能。**加大 Backlog 是性价比最高的优化手段**。
### 复制背压(Replication Backlog)
每个 Master 内部维护一个固定大小的 **环形缓冲区**(默认仅 1MB),这是实现增量同步的关键:
```mermaid
flowchart LR
WriteHead["写入指针(新命令)"] -->|"追加写入"| Buffer["repl-backlog-buffer
环形缓冲区"]
Buffer -->|"读取并发送"| ReadHead["读取指针(发给 Replica)"]
Buffer -->|"空间不足时"| Overwrite["覆盖最旧数据"]
```
> [!NOTE] 什么是"环形缓冲区"?
> 想象一个**圆形的笔记本**,只有固定的 100 页。你从第 1 页开始记笔记,记满了就回到第 1 页覆盖重写。Replica 落后得越多,它需要"回看"的内容就越远。如果它的位置已经被覆盖了,就只能全量重新抄一本——这就是全量同步。
>
> 所以 Backlog 的大小直接决定了:**Replica 最多能断线多久还能增量恢复**。
```conf
# 相关配置
repl-backlog-size 256mb # 缓冲区大小,增大允许更长断线恢复
repl-backlog-ttl 3600 # 无人订阅时多久自动释放(秒)
```
**经验估算公式**:假设业务 QPS = 10,000,每条命令平均 50 字节,则每秒消耗 ~500KB 缓冲区。
- backlog = 1MB → 约支持 **2 秒** 断线恢复
- backlog = 256MB → 约支持 **8 分钟** 断线恢复
### 大 Key 问题
> [!QUESTION] 一个 key 有 5MB 大小,会有什么问题?
> - **写入阻塞**:Redis 是单线程的,写入一个 5MB 的 Hash/Set 会阻塞其他所有请求几毫秒甚至更久
> - **同步瓶颈**:这条命令不仅要在 Master 上执行,还要通过网络传给每个 Replica,相当于同一条 5MB 的数据在内网里传 N 次
> - **内存尖峰**:`BGSAVE` 时 fork 子进程,大 key 会导致 COW(Copy-On-Write)产生大量内存拷贝
> [!SUMMARY] 如何发现大 Key?
> ```bash
> redis-cli --bigkeys # 在线扫描(谨慎!高 QPS 环境有性能影响)
> scan 0 MATCH * COUNT 100000 # 逐页扫描,用 type/hashlist/set 分别评估
> ```
**解决方案**:
- **写入侧**:拆分大 key 为多个小 key(例如一个存储用户全部订单的 Hash → 按月份拆分为 `user:orders:2026-05`、`user:orders:2026-06` 等多个小 Hash)
- **同步侧**:启用无盘复制(`repl-diskless-sync yes`),Master 直接通过 socket 把 RDB 发给所有 Replica,避免写磁盘再读磁盘的双重开销
## 二、级联复制(拓扑优化)
### 为什么要级联?
假设 Master 需要服务 10 个 Replica,每个 Replica 每秒同步 ~500KB 的命令流,那 Master 每秒要输出 **5MB 的复制流量**。而且每个 Replica 断线重连都可能触发全量同步——Master 的网络带宽和 CPU(BGSAVE 的 fork)会成为瓶颈。
> [!QUESTION] 怎么降低 Master 的压力?
> 答案:不让所有 Replica 都直接连 Master,而是让部分 Replica "转发"数据给其他 Replica,形成**树形拓扑**。
```mermaid
flowchart LR
Master["Master
只服务 2 个 Replica"]
R1["Replica-1
(同时作为下游的 Master)"]
R2["Replica-2"]
R3["Replica-3
(从 R1 同步)"]
R4["Replica-4
(从 R1 同步)"]
Master -->|"直接同步"| R1
Master -->|"直接同步"| R2
R1 -->|"级联同步"| R3
R1 -->|"级联同步"| R4
```
> [!TIP] 最佳实践
> - 只有 1~2 个 Replica 直连 Master,其余通过级联获取数据
> - 级联的 Replica 也能配置为接受下游连接(Redis 原生支持,无需额外设置)
> - **缺点**:级联层级越深,末端 Replica 的数据延迟越大。通常 **两层** 是合理的上限
## 三、只读副本注意事项
Replica 默认是 **只读** 的(`replica-read-only yes`),这不是 Redis 故意限制你,而是有充分的技术原因:
> [!WARNING] 为什么不应该在 Replica 上写数据?
> 想象一下:你在 Replica 上写入了 `SET foo bar`,但 Master 上没有这个写入。下一次同步发生时,Master 会把自己的数据集"覆盖"到 Replica 上——你写的数据**悄无声息地消失了**。更糟糕的是,如果你在 Replica 上 `DEL` 一个不存在的 key,这个命令可能会被传播到 Master,导致 Master 上原本存在的数据被删掉。
>
> **结论**:在 Replica 上写数据,本质上是在制造**数据不一致的定时炸弹**。
### Replica 的关键配置详解
```conf
# --- 副本如何发现 Master ---
# 在 Docker/NAT 环境下,容器内部 IP 和宿主机 IP 不同。
# 如果不设置 announce-ip,Sentinel 和其他节点可能会记录容器内部 IP,
# 导致跨网络访问失败。
replica-announce-ip 192.168.1.20 # 对外宣告的 IP
replica-announce-port 6379 # 对外宣告的端口
# --- Replica 是否可被查询 ---
# 这组配置决定:当 Replica 和 Master 断开连接时,还能不能对外服务?
replica-serve-stale-data yes # yes=断线时仍返回旧数据(可用性优先)
# no=断线时直接报错(一致性优先)
replica-read-only yes # 只读保护,防止误写入导致数据不一致
replica-lazy-flush no # FULLRESYNC 时清空旧数据的策略:
# no=立即 flush(默认),yes=惰性清理
# --- 副本同步相关 ---
repl-diskless-sync no # 无盘复制:Master 不写 RDB 文件,
# 直接通过 socket 发给 Replica(适合 SSD 环境)
repl-diskless-sync-delay 5 # 开启无盘复制后,等几秒让多个 Replica 同时接收
repl-timeout 60 # 同步超时阈值(秒),超时则断开重连
```
> [!QUESTION] `replica-serve-stale-data` 应该设为 yes 还是 no?
> 这取决于你的业务对**可用性 vs 一致性**的取舍:
> - **电商首页**、**推荐列表**等场景:设为 `yes`,返回稍微过期的数据总比报错好
> - **库存扣减**、**余额查询**等场景:设为 `no`,宁可不可用也不能返回错误数据
## 四、核心参数调优参考
> [!NOTE] 根据业务特点选择合适的参数组合
### 方案 A:极致性能优先
```conf
# 适用于读 QPS > 50k,对一致性要求不高
repl-backlog-size 256mb # 大容量 backlog 减少全量同步
repl-diskless-sync yes # 无盘复制加速 RDB 传输
replica-lazy-flush no # 确保同步前快速清理旧数据
```
### 方案 B:一致性优先
```conf
# 适用于金融、账务等场景
WAIT 1 100 # 写入时确认至少 1 个副本成功
repl-backlog-size 512mb # 更大缓冲区容纳更多增量命令
```
### 常用调试命令
```bash
# 查看当前复制状态
redis-cli INFO replication
# 输出示例:
# role:master ← 或 replica
# connected_slaves:3
# master_repl_offset:12345678
# repl_backlog_active:1
# repl_backlog_size:268435456
```
## 五、Sentinel 哨兵机制
### 为什么需要 Sentinel?
上文介绍了主从复制,但有一个关键问题没解决:**Master 宕机了怎么办?**
> [!QUESTION] 没有 Sentinel 时,Master 故障的处理流程是什么?
> 1. 运维人员收到告警(可能是半夜 3 点)
> 2. 登录服务器,确认 Master 确实挂了
> 3. 手动挑一个数据最新的 Replica,执行 `SLAVEOF NO ONE` 提升为新 Master
> 4. 让其他 Replica 指向新 Master
> 5. 通知客户端切换连接地址
>
> 整个过程可能需要 **几分钟甚至更久**,而且容易出错。Sentinel 要做的就是把这整套流程**自动化**。
### 架构组成
Sentinel 本身也是一个 Redis 进程(只是不处理普通数据读写),它的工作就是**盯着 Master 和 Replica,必要时自动故障转移**。
```mermaid
flowchart TD
S1["Sentinel-1"]
S2["Sentinel-2"]
S3["Sentinel-3"]
MasterNode["Master"]
RepNode["Replica"]
S1 <-->|"互相探测心跳"| S2
S1 <-->|"互相探测心跳"| S3
S2 <-->|"互相探测心跳"| S3
S1 <-->|"监控 Master/Replica 状态"| MasterNode
S2 <-->|"监控 Master/Replica 状态"| MasterNode
S3 <-->|"监控 Master/Replica 状态"| MasterNode
MasterNode <-->|"主从同步"| RepNode
```
> [!QUESTION] 为什么需要奇数个 Sentinel?
> 故障转移需要**多数派投票**才能决策(和选举类似)。3 个 Sentinel 中有 2 个同意就能执行;但如果只有 2 个,一旦 1 个挂了,剩下的 1 个永远达不到多数,故障转移就无法执行。偶数个(比如 4 个)也浪费——4 个节点允许 1 个故障,和 3 个一样,却多消耗了一份资源。
>
> **记住这个公式:N 个 Sentinel 允许 (N-1)/2 个故障**。3 个允许 1 个故障,5 个允许 2 个故障。
### 核心功能
Sentinel 有三大核心职责:**监控**、**通知**和**故障转移**。我们逐一拆解。
#### 1. 监控(Monitoring)——"心跳检测"
每个 Sentinel 每隔 1 秒向 Master 和 Replica 发送 `PING` 命令,期待收到 `PONG` 回复。如果在 `down-after-milliseconds` 时间内没有收到回复,Sentinel 就认为这个节点"有问题了"。
但一个 Sentinel 的判断可能是误判(比如只是 Sentinel 和 Master 之间的网络抖动),所以需要多个 Sentinel 协同确认:
```mermaid
flowchart TD
A["Sentinel-1 向 Master 发 PING"] --> B{"收到 PONG?"}
B -->|"是"| C["一切正常, 继续监控"]
B -->|"否"| D["标记 Master 为 SDOWN
(主观下线: 我觉得它挂了)"]
D --> E{"询问其他 Sentinel:
你们觉得 Master 还活着吗?"}
E -->|"Quorum 个 Sentinel
都认为挂了"| F["标记 Master 为 ODOWN
(客观下线: 大家都确认它挂了)"]
E -->|"没达到 Quorum"| G["可能是网络局部问题
保持 SDOWN, 继续观察"]
F --> H["进入故障转移流程"]
```
> [!NOTE] SDOWN vs ODOWN,一句话总结
> - **SDOWN**(Subjectively Down)= "我觉得它挂了"——单个 Sentinel 的主观判断
> - **ODOWN**(Objectively Down)= "大家都确认它挂了"——达到 Quorum 个 Sentinel 的客观共识
>
> 只有 ODOWN 才会触发真正的故障转移,SDOWN 只是一个"预警"状态。
#### 2. 选主(Leader Election)——"谁来操刀故障转移"
当 Master 被判定 ODOWN 后,**不能所有 Sentinel 都去执行故障转移**(否则会乱套)。需要先选举一个 **Sentinel Leader** 来统一操刀:
```mermaid
sequenceDiagram
participant S1 as Sentinel-1
participant S2 as Sentinel-2
participant S3 as Sentinel-3
Note over S1,S3: "Master 被判定 ODOWN, 需要选出一个 Leader"
S1->>S2: "我要当 Leader, 请投我一票"
S1->>S3: "我要当 Leader, 请投我一票"
S2->>S1: "同意, 投给你了"
S3->>S1: "同意, 投给你了"
Note over S1: "获得多数票, 我是 Leader 了"
S1->>S1: "开始执行故障转移..."
```
> [!TIP] 投票规则细节
> - **先到先得**:每个 Sentinel 在一轮投票中只能投一票,投给第一个来拉票的
> - **一轮只选一个**:如果多个 Sentinel 同时发起选举,票数不够就都不当选,等随机超时后重新发起(类似以太网的 CSMA/CD 避免冲突)
> - **任期制**:每次故障转移都有一个 `configEpoch`(类似 Raft 的 term),防止旧 Leader 误操作
### 故障转移步骤详解
Sentinel Leader 当选后,按以下顺序执行故障转移。整个过程通常在 **几秒到几十秒** 内完成:
```mermaid
flowchart TD
A["Step 1: 选出最佳 Replica"] --> B["Step 2: 提升为新 Master"]
B --> C["Step 3: 让其他 Replica 转向新 Master"]
C --> D["Step 4: 通知客户端"]
D --> E["Step 5: 旧 Master 回来后自动降级"]
```
**Step 1:选出最佳 Replica** — "谁来接班?"
从所有健康的 Replica 中选出数据最新的一个,选择标准按优先级排序:
| 优先级 | 评估维度 | 含义 | 举例 |
|--------|---------|------|------|
| 1(最高) | **复制偏移量最大** | 同步进度最接近 Master | Replica-A offset=10000, Replica-B offset=9998 → 选 A |
| 2 | **`replica-priority` 最小** | 配置中人为指定的优先级 | priority=10 优先于 priority=100;priority=0 表示"永不选我" |
| 3(最低) | **Run ID 字典序最小** | 纯粹的 tie-breaker | 两个 Replica 前两项完全相同,选 Run ID 字母序靠前的 |
**Step 2:提升为新 Master**
Sentinel 向选中的 Replica 发送 `SLAVEOF NO ONE`,使其断开与旧 Master 的复制关系,变成独立的 Master。
**Step 3:让其他 Replica 转向新 Master**
Sentinel 向其余 Replica 发送 `SLAVEOF `,它们开始从新 Master 同步数据。
> [!NOTE] `parallel-syncs` 的作用
> Sentinel 配置中的 `parallel-syncs` 控制**同时转向新 Master 的 Replica 数量**。设为 1 表示一个接一个地切换,避免所有 Replica 同时中断服务去同步(同步期间可能短暂不可用)。
**Step 4:通知客户端**
Sentinel 通过 Pub/Sub 机制在 `+switch-master` 频道广播新 Master 的地址。支持 Sentinel 协议的客户端库(如 go-redis)会自动订阅并切换。
**Step 5:旧 Master 回来后怎么办?**
旧 Master 恢复上线后,Sentinel 会自动把它配置为新 Master 的 Replica——它从"领导"变成了"下属",不会再抢回 Master 身份。
### Sentinel 配置文件
```conf
# ── 核心配置:监控哪个 Master ──
sentinel monitor mymaster 192.168.1.10 6379 2
# ↑ Master地址 ↑ 端口 ↑ Quorum(至少几个 Sentinel 同意才判定 ODOWN)
# ── 认证 ──
sentinel auth-pass mymaster your-password # Master 设置了密码时必填
# ── 三个最重要的时间参数 ──
# 1. 主观下线阈值:Master 多久没回 PING 就标记 SDOWN
# 不要太低!网络抖动 1~2 秒在生产环境很常见
sentinel down-after-milliseconds mymaster 30000 # 30 秒(推荐 15~30 秒)
# 2. 故障转移超时:一次 Failover 最多花多长时间
# 超时则认为本次转移失败,等下次重试
sentinel failover-timeout mymaster 180000 # 3 分钟
# 3. 并行同步数:同时转向新 Master 的 Replica 个数
# 设为 1 = 逐个切换,保证至少有部分 Replica 始终可用
sentinel parallel-syncs mymaster 1
```
> [!WARNING] Sentinel 配置陷阱
> - **`down-after-milliseconds` 不要太低**(如 1s)——网络抖动会触发误判,导致不必要的故障转移。建议 15~30 秒
> - **`failover-timeout` 不宜太短**——如果 Master 数据量大,Replica 同步需要时间,太短会导致 Failover 反复超时失败
> - **`parallel-syncs` 设为 1**——虽然切换慢一点,但保证服务不停;设为 0 或很大则所有 Replica 同时中断服务去同步
> - **生产环境建议手动维护 Sentinel 配置文件**——虽然 Sentinel 有自动感知功能,但配置文件中的信息更可控
> - **Redis 6+ ACL 兼容**——启用 ACL 后,用 `sentinel user myuser >password` 替代 `auth-pass`
### Sentinel 下的客户端连接
> [!QUESTION] 故障转移后,客户端怎么知道新 Master 的地址?
> 传统方式是客户端硬编码 Redis 地址——Master 换了 IP,所有客户端都得改配置重启。Sentinel 方案下,客户端**直接连接 Sentinel**(而不是 Redis),由 Sentinel 告诉客户端"谁是当前的 Master"。
>
> 主流语言的 Redis 客户端库都内置了 Sentinel 支持,原理是:
> 1. 启动时向 Sentinel 查询当前 Master 地址
> 2. 连接 Master 进行读写
> 3. 如果连接失败,重新向 Sentinel 查询新地址
**Go(go-redis)示例**——客户端自动发现和切换:
```go
rdb, err := redis.NewFailoverClient(&redis.FailoverOptions{
MasterName: "mymaster", // Sentinel 中配置的 Master 名称
SentinelAddrs: []string{"sentinel1:26379", "sentinel2:26379", "sentinel3:26379"},
Password: "your-password",
MaxRetries: 3, // 连接失败重试次数
RetryDelay: 500 * time.Millisecond,
})
// 客户端自动感知 Master:读请求走 Replica(如果配置了读写分离),写请求走 Master
_ = rdb.Set(ctx, "order:12345", data, 0) // 写 → Master
val, _ := rdb.Get(ctx, "order:12345").Result() // 读 → 可能走 Replica
```
**Python(redis-py)示例**:
```python
from redis.sentinel import Sentinel
# 连接 Sentinel 节点
sentinel = Sentinel([
('sentinel1', 26379),
('sentinel2', 26379),
('sentinel3', 26379),
])
# 获取 Master 和 Slave 连接(自动发现 + 故障切换)
master = sentinel.master_for('mymaster', password='your-password') # 用于写
slave = sentinel.slave_for('mymaster', password='your-password') # 用于读
master.set('key', 'value') # 写入 Master
value = slave.get('key') # 从 Replica 读取
```
## 六、生产实践 checklist
### 部署前 Checklist
- [ ] **Sentinel 节点数 ≥ 3,且部署在不同机器/可用区** — 避免单机故障导致 Sentinel 丧失多数派
- [ ] **Master + Replica 数量建议 1:2 ~ 1:3** — 太多 Replica 会加重 Master 同步负担;太少则冗余不足
- [ ] **配置 `replica-priority`** — 不应被选为 Master 的 Replica 设为 `0`(如配置较低的从节点)
- [ ] **持久化策略保持一致** — Master 和 Replica 都应开启 RDB 或 AOF,否则故障转移后新 Master 无持久化可能导致数据丢失
### 监控项
日常巡检重点关注以下指标(建议每秒采样,配合 Prometheus/Grafana 看趋势):
```bash
# 复制状态 —— 最核心,异常时第一时间查这个
redis-cli INFO replication
# 关键字段:
# connected_slaves:3 ← 在线 Replica 数量,突然减少说明有 Replica 掉线
# master_repl_offset:12345678 ← Master 当前写入偏移量,持续增长说明正常
# repl_backlog_active:1 ← Backlog 是否活跃
# slave0:offset=12345600,lag=0 ← 每个 Replica 的同步偏移量和延迟
# 内存 —— BGSAVE 期间内存会临时翻倍(fork 子进程)
redis-cli INFO memory
# 关键字段:
# used_memory_human:2.50GB ← 同步期间如果骤增,可能触发 OOM
# 每秒操作数 —— 发现流量突增或突降
redis-cli INFO stats
# 关键字段:
# instantaneous_ops_per_sec:15000
```
### 故障排查流程
```mermaid
flowchart TD
A["发现 Master 异常"] --> B{"Sentinel 是否已 ODOWN?"}
B -->|"是"| C["自动 Failover 中..."]
B -->|"否"| D["检查网络 / CPU / 大 Key 阻塞"]
C --> E["Failover 成功?"]
E -->|"是"| F["客户端已自动切换 ✓"]
E -->|"否"| G["手动干预:
SENTINEL FAILOVER mymaster"]
D --> H["问题解决后
重新加入复制拓扑"]
```
### 常见坑点汇总
| 坑 | 现象 | 根因 | 解决方案 |
|---|------|------|------|
| **Docker 环境 IP 错乱** | Sentinel 把容器内部 IP(如 172.17.0.x)当成 Master 地址告诉客户端,客户端连不上 | Docker 网络与宿主机网络不同 | 设置 `replica-announce-ip` 为宿主机 IP |
| **脑裂(Split Brain)** | 网络分区后出现两个 Master 同时接受写入,分区恢复后一个 Master 的数据被覆盖丢失 | Master 与 Sentinel 网络断开,但与部分客户端仍连通 | 配置 `min-replicas-to-write` 保护 |
| **BGSAVE 时 OOM** | 同步期间 Redis 内存突然翻倍,被 OOM Killer 杀掉 | fork 子进程使用 COW 机制,内存紧张时几乎复制整个数据集 | 关闭 THP、增大 swap、预留 50% 内存空间 |
> [!NOTE] 脑裂是什么?怎么防?
> **脑裂**指的是:Master 实际还活着(只是和 Sentinel 之间网络断了),但 Sentinel 以为它挂了,选出了一个新 Master。这时候**两个 Master 同时接受写入**。网络恢复后,旧 Master 变成新 Master 的 Replica,它的数据会被覆盖——写入旧 Master 的数据就丢了。
>
> **防御措施**:在 Master 端配置"至少要有 N 个 Replica 在线才接受写入":
> ```conf
> # 至少 1 个 Replica 在线且延迟不超过 10 秒,Master 才接受写入
> min-replicas-to-write 1
> min-replicas-max-lag 10
> ```
> 这样当 Master 被隔离时(没有 Replica 在线),它会**拒绝写入**,而不是默默接受数据最终丢失的写操作。
## 七、局限性与替代方案
Sentinel 方案虽然解决了"高可用"问题,但它有明确的边界。理解这些边界才能选对方案:
| 场景 | Sentinel 能解决吗? | 为什么? | 应该用什么? |
|------|----------------|---------|-----------|
| Master 宕机自动恢复 | ✅ | 这正是 Sentinel 的核心功能 | — |
| 读请求太多,单节点扛不住 | ✅ | 增加 Replica 分摊读压力 | 主从复制 |
| **写请求太多,单 Master 瓶颈** | ❌ | 只有一个 Master 处理所有写入 | [[hhs/Redis/07-集群方案]] Cluster |
| **数据量超过单机内存** | ❌ | 每个节点都存全量数据 | Cluster(数据分片) |
| **跨机房容灾** | ❌ | 跨网段延迟太高,Sentinel 心跳会误判 | 异地多活架构 |
> [!QUESTION] 什么时候该从 Sentinel 升级到 Cluster?
> 当你遇到以下任一情况时,就该考虑 Cluster 了:
> - 单 Master 写 QPS 持续超过 5w,垂直扩展(换更强的机器)已到极限
> - 数据量超过单机内存(如 100GB+),且无法通过压缩优化
> - 需要水平扩展写入能力(多个 Master 分片处理不同 key 的写入)
>
> → 详见 [[hhs/Redis/07-集群方案]]
## 关联笔记
- [[hhs/Redis/07-集群方案]] — Cluster 架构(水平扩展)
- [[hhs/Redis/11-运维与性能调优]] — 监控与告警配置
- [[hhs/Redis/05-AOF持久化]] — AOF 与 RDB 结合的高可用策略
- [[hhs/EXAM/Week05]] — Docker Compose 中的部署示例