---
tags: [Redis, 缓存, 高可用, 主从, Sentinel]
create time: 2026-05-15 18:13
---
# Redis 主从复制与 Sentinel
## 概述
在生产环境中,单节点 Redis 存在两大问题:**写入能力有上限**、**宕机即停服**。本节介绍两个构建基础高可用的核心机制——
| 机制 | 解决什么问题 | 一句话说明 |
|------|------------|----------|
| **主从复制(Replication)** | 数据冗余 + 读扩展 | Master 处理写操作,Replica 自动同步数据并承担读请求 |
| **Sentinel(哨兵)** | 人工恢复慢 | 自动检测 Master 故障,提升最佳 Replica 为新 Master |
> [!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 半同步
> [!QUESTION] 为什么 Redis 选择异步复制?
> 如果等 Replica 确认后 Master 才返回客户端,那网络延迟 = 写入延迟。对于追求低延迟的场景(微秒级),这是不可接受的。
Redis 默认**异步复制**——Master 写完即返回客户端,不等待 Replica 确认。这保证了极致低延迟,代价是极端情况下可能丢失数据:
```text
场景: 写 A=1 → Master 返回 OK → Master 宕机(还没传到 Replica)→ 重启后 A 不存在
```
若业务需要 **至少一份副本落盘** 的强一致性保证,可配合 Lua + `wait` 命令实现弱半同步:
```go
// Go: 确保至少 N 个副本成功写入
numReplicas := 1 // 至少 1 个 replica 收到
timeout := 100 // 超时 100ms
result := client.Do(ctx, "WAIT", numReplicas, timeout).Int()
// result == 1 → 至少有 1 个副本已同步
```
> [!TIP] WAIT 的代价
> 阻塞当前线程直到满足条件或超时。高频写场景慎用,建议仅在关键事务中使用。
### 全量同步 vs 增量同步
> [!NOTE] PSync:一次连接,多次复用
> Redis 2.8+ 引入 PSYNC 协议替代旧版 SYNC,核心优势:**断线重连后可增量同步**。
```mermaid
sequenceDiagram
participant R as Replica
participant M as Master
Note over R,M: ── 阶段 1: 全量同步(首次或断线过长)──
R->>M: PSYNC ? -1 (首次连接)
M->>M: BGSAVE → dump.rdb.tmp
M-->>R: +CONTENTS dump.rdb
R->>R: 重写本地数据集
loop 同步期间的新命令
M->>M: 追加到 repl-backlog-buffer
end
M-->>R: +CONTENTS buf (缓冲命令流)
R->>R: 重放缓冲命令
Note over R,M: ── 阶段 2: 增量同步(后续心跳)──
loop 通常 1 秒一次
R->>M: PSync
M->>R: 发送 offset 之后的命令
end
```
#### 什么情况下触发全量同步?
| 触发条件 | 说明 |
|---------|------|
| 首次连接 | Replica 为空,必须下载完整 RDB |
| Master 重启 | Run ID 改变,无法增量恢复 |
| Replica 手动 SLAVEOF | 主动请求全量同步 |
| Backlog 过小 | 断线时间超出 repl-backlog-size 缓存容量 |
| config-resetstat 执行 | 元数据重置导致无法增量 |
### 复制背调(Replication Backlog)
每个 Master 内部维护一个固定大小的环形缓冲区(默认 1MB):
```mermaid
flowchart LR
WriteHead["写入指针"] -->|"新命令流入"| Buffer["repl-backlog-buffer
环形缓冲区(默认1MB)"]
ReadHead["读取指针"] -->|"命令回放给 Replica"--> Buffer
Buffer -->|"溢出"`覆盖最旧数据`"
```
```conf
# 相关配置
repl-backlog-size 256mb # 缓冲区大小,增大允许更长断线恢复
repl-backlog-ttl 3600 # 无人订阅时多久自动释放(秒)
```
**经验估算公式**:假设业务 QPS = 10,000,每条命令平均 50 字节,则每秒消耗 ~500KB 缓冲区。
- backlog = 1MB → 约支持 **2 秒** 断线恢复
- backlog = 256MB → 约支持 **8 分钟** 断线恢复
### 大键分裂问题(Big Key Split)
当某个 key 体积很大时(如几 MB 的 Hash/Set),一次 `SET` 会阻塞整个 Redis 进程数毫秒甚至更久。对 Replica 来说,这个命令在网络上传输也会成为瓶颈。
> [!SUMMARY] 如何发现大 Key?
> ```bash
> redis-cli --bigkeys # 在线扫描(谨慎!高 QPS 环境有性能影响)
> scan 0 MATCH * COUNT 100000 # 逐页扫描,用 type/hashlist/set 分别评估
> ```
解决方案:
- 写入侧:**分拆大 key** 为多个小 key(如按用户 ID 哈希分散)
- 同步侧:增大 `repl-diskless-sync-delay` 让多个 Replica 同时接收,避免重复传输
## 二、级联复制(拓扑优化)
当 Replica 数量增多时,每个都连 Master 会造成带宽压力:
```mermaid
flowchart LR
Master["Master"]
R1["Replica-1
(也接受从连接)"]
R2["Replica-2"]
R3["Replica-3"]
R4["Replica-4"]
Master --> R1
Master --> R2
R1 --> R3
R1 --> R4
```
> [!TIP] 最佳实践
> 只有 1~2 个 Replica 直接连 Master,其余通过级联获取数据。降低 Master 的网络和 CPU 负担。
## 三、只读副本注意事项
```conf
replica-read-only yes # 默认值
```
> [!WARNING] 禁止在 Replica 上写操作
> 即使关闭 `read-only`,Replica 上的写入在主从重新同步时会被清掉。而且 `DEL` 一个不存在的 key 也会触发额外命令发送给 Master。
### Replica 的其他关键配置
```conf
# --- 副本如何发现 Master ---
replica-announce-ip 192.168.1.20 # 对外宣告的 IP(Docker/NAT 环境下必设)
replica-announce-port 6379 # 对外宣告的端口
# --- Replica 是否可被查询 ---
replica-lazy-flush no # FULLRESYNC 后清空旧数据策略:no=立即 flush
replica-serve-stale-data yes # Master 不可达时是否继续服务请求(默认是)
replica-read-only yes # 只读保护
# --- 副本同步相关 ---
repl-diskless-sync no # 无盘复制开关
repl-diskless-sync-delay 5 # 等待其他副本同时连接的延迟(秒)
repl-timeout 60 # 同步超时阈值
```
## 四、核心参数调优参考
> [!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 哨兵机制
### 架构组成
```mermaid
flowchart TD
S1["Sentinel-1"]
S2["Sentinel-2"]
S3["Sentinel-3"]
MasterNode["Master"]
RepNode["Replica"]
S1 <-->|心跳 PING/PONG| S2
S1 <-->|心跳 PING/PONG| S3
S2 <-->|心跳 PING/PONG| S3
S1 <--> MasterNode
S2 <--> MasterNode
S3 <--> MasterNode
MasterNode <--> RepNode
```
> [!QUESTION] 为什么需要奇数个 Sentinel?
> Sentinel 选举采用多数派原则(Quorum)。3 个节点中 2 个达成共识即可;5 个中需要 3 个。偶数增加 1 个 Sentinel 不会带来额外投票优势,反而浪费资源。
### 核心功能
#### 1. 监控(Monitoring)
```bash
# Sentinel 定期向 Master/Replica 发 PING
# Master 必须响应:PING → PONG 或 INFO
# Replica 同理
# 三种下线判断
1. SDOWN (Subjectively Down) — 某个 Sentinel 认为不可达
2. ODOWN (Objectively Down) — Quorum 数量的 Sentinel 都认为不可达
3. Failover in progress — 进入故障转移流程
```
#### 2. 选主(Leader Election)
```mermaid
sequenceDiagram
participant S1 as Sentinel-1
participant S2 as Sentinel-2
participant S3 as Sentinel-3
S1->>S2: 提议自己为 leader (SEND ME YOUR VOTE)
S2->>S1: OK (投票给 S1)
S3->>S1: OK (投票给 S1)
S1->>S2: 我当选 leader
Note over S1,S3: ⚡ 任意 Sentinel 都可以主动发起选举
```
### 故障转移步骤
当 Master 被判定 ODOWN 后,Sentinel Leader 执行以下流程:
```mermaid
flowchart TD
A["1. 选出最佳 Replica"] --> B["评估标准:
复制偏移量 > 优先级 > RunID"]
B --> C["SLAVEOF NO ONE
提升为新 Master"]
C --> D["其他 Replica → SLAVEOF new-master"]
D --> E["更新集群元数据"]
E --> F["通知客户端新地址"]
```
**最佳 Replica 选择标准**(按优先级排序):
1. **复制偏移量最大** — 离最新数据的 Replica 优先
2. **`replica-priority` 最小** — 值越小越优先当选(0 = 不可当选)
3. **Run ID 字典序最小** — 作为最终 tie-breaker
### Sentinel 配置文件
```conf
sentinel monitor mymaster 192.168.1.10 6379 2
# ↑ name ↑ host port quorum(达成SDOWN共识的个数)
sentinel auth-pass mymaster your-password
# 多久无响应即标记 SDOWN(毫秒)
sentinel down-after-milliseconds mymaster 30000
# 故障转移超时(毫秒),超过则失败重试
sentinel failover-timeout mymaster 180000
# 最多多少 Replica 同时与新 Master 同步
sentinel parallel-syncs mymaster 1
```
> [!WARNING] Sentinel 配置陷阱
> - `down-after-milliseconds` 不要设太低(如 1s),网络抖动会导致误判;建议 **15~30 秒**
> - `failover-timeout` 不宜太短,防止频繁重试加重系统负载
> - `parallel-syncs` 设为 1 可避免转移期间所有 Replica 同时不可用
> - **生产环境建议手动维护 Sentinel 配置**,避免自动感知的不确定性
> - Redis 6+ 启用 ACL 后,需配置 `sentinel user/myuser password/mypass` 而非 `auth-pass`
### Sentinel 下的客户端连接
Go 和 Python 生态都提供了原生支持——客户端内部自动发现 Master/Replica,故障时重新连接。
```go
// go-redis: 原生 Sentinel 模式
rdb, err := redis.NewFailoverClient(&redis.FailoverOptions{
MasterName: "mymaster",
SentinelAddrs: []string{"sento1:26379", "sento2:26379", "sento3:26379"},
Password: "your-password",
MaxRetries: 3,
RetryDelay: 500 * time.Millisecond,
})
// 读请求走 Replica,写请求走 Master
_ = rdb.Get(ctx, "key")
```
```python
# redis-py Sentinel
from redis.sentinel import Sentinel
sentinel = Sentinel([('sento1', 26379), ('sento2', 26379), ('sento3', 26379)])
master = sentinel.master_for('mymaster', password='your-password') # 写
slave = sentinel.slave_for('mymaster', password='your-password') # 读
```
## 五、生产实践 checklist
### 部署前
- [ ] **Sentinel 节点数 ≥ 3**,奇数个,部署在不同可用区或机器上
- [ ] **Master + Replica 数量建议 1:2 ~ 1:3**,过多会影响同步延迟
- [ ] **`replica-priority` 设值**:不可作为 Master 的设为 `0`
- [ ] **持久化策略一致**:Master 和 Replica 的 RDB/AOF 策略应相同
### 监控项
```bash
# 关键指标(每秒采样)
INFO replication # connected_slaves, master_repl_offset
INFO memory # used_memory_human (同步期间会突增)
INFO stats # instantaneous_ops_per_sec
```
### 故障排查流程
```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 | 设置 `replica-announce-ip` |
| 脑裂(Split Brain) | 网络分区后 Master 仍处理写请求 | 用 `min-replicas-to-write 1` 保护 |
| OOM during BGSAVE | fork 子进程导致内存翻倍 | 关闭 THP,增大 swap,限制 maxmemory |
> [!NOTE] 防脑裂配置
> ```conf
> # Master 端配置:至少 N 个 Replica 在线才接受写入
> min-replicas-to-write 1
> # 对应的最大允许延迟(秒),超时视为掉线
> min-replicas-max-lag 10
> ```
## 六、局限性与替代方案
| 问题 | Sentinel 无法解决 |
|------|----------------|
| 单 Master 写入瓶颈 | ❌ 只有一个 Master |
| 数据量超限单机容量 | ❌ |
| 跨机房容灾 | ❌(Sentinel 跨网段延迟太高) |
> [!TIP] 需要水平扩展?看 Cluster 方案 → [[hhs/Redis/07-集群方案]]
## 关联笔记
- [[hhs/Redis/07-集群方案]] — Cluster 架构(水平扩展)
- [[hhs/Redis/09-运维调优]] — 监控与告警配置
- [[hhs/Redis/04-AOF持久化]] — AOF 与 RDB 结合的高可用策略
- [[hhs/EXAM/Week05]] — Docker Compose 中的部署示例