This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hhs/Redis/06-主从与哨兵.md
T
2026-05-17 00:06:11 +08:00

412 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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<br/>写操作入口"]
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 <masterRunId> <offset>
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<br/>环形缓冲区(默认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<br/>(也接受从连接)"]
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["评估标准:<br/>复制偏移量 > 优先级 > RunID"]
B --> C["SLAVEOF NO ONE<br/>提升为新 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["手动干预:<br/>SENTINEL FAILOVER mymaster"]
D --> H["问题解决后<br/>重新加入复制拓扑"]
```
### 常见坑点汇总
| 坑 | 现象 | 解决 |
|---|------|------|
| 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 中的部署示例