29 KiB
tags, create time
| tags | create time | |||||
|---|---|---|---|---|---|---|
|
2026-05-15 18:13 |
Redis 主从复制与 Sentinel
概述
从单机困境说起
假设你的 Redis 跑在一台服务器上,所有读写都走这一个实例。随着业务增长,两个现实问题会逐渐暴露:
- 读性能瓶颈:热点 key 被反复访问,单实例 QPS 顶到天花板(通常 10w 左右),请求开始排队
- 单点故障风险:服务器宕机 = 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 的前置知识
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 半同步
复制的核心矛盾是:性能 和 数据安全 不可兼得。我们先看两种策略的区别:
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 的事"后台慢慢同步"。代价是极端情况下可能丢数据:
场景: 写 A=1 → Master 返回 OK → Master 突然宕机(数据还没来得及传到 Replica)
→ Replica 上没有 A=1 → 用 Replica 接管后,这个写入就丢了
[!NOTE] 这个丢数据的概率高吗? 正常情况下极低——因为 Master 到 Replica 的同步几乎是实时的(毫秒级),只有在 Master 写入后立刻宕机的极短窗口内才会丢。这就像你刚存完钱、银行还没来得及记账就停电了——概率很小,但在金融场景下不能接受。
如果业务要求"至少一份副本确认"呢?
Redis 提供了 WAIT 命令,可以让客户端阻塞等待指定数量的副本确认同步完成。这不算严格的半同步(因为 Master 已经先写入并返回了),但能大幅降低丢数据的概率:
// 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 改为"只抄你缺的那几页"。
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 <runId> <offset>, 然后发送 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 <masterRunId> <myOffset>
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),这是实现增量同步的关键:
flowchart LR
WriteHead["写入指针(新命令)"] -->|"追加写入"| Buffer["repl-backlog-buffer<br/>环形缓冲区"]
Buffer -->|"读取并发送"| ReadHead["读取指针(发给 Replica)"]
Buffer -->|"空间不足时"| Overwrite["覆盖最旧数据"]
[!NOTE] 什么是"环形缓冲区"? 想象一个圆形的笔记本,只有固定的 100 页。你从第 1 页开始记笔记,记满了就回到第 1 页覆盖重写。Replica 落后得越多,它需要"回看"的内容就越远。如果它的位置已经被覆盖了,就只能全量重新抄一本——这就是全量同步。
所以 Backlog 的大小直接决定了:Replica 最多能断线多久还能增量恢复。
# 相关配置
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?
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,形成树形拓扑。
flowchart LR
Master["Master<br/>只服务 2 个 Replica"]
R1["Replica-1<br/>(同时作为下游的 Master)"]
R2["Replica-2"]
R3["Replica-3<br/>(从 R1 同步)"]
R4["Replica-4<br/>(从 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 的关键配置详解
# --- 副本如何发现 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:极致性能优先
# 适用于读 QPS > 50k,对一致性要求不高
repl-backlog-size 256mb # 大容量 backlog 减少全量同步
repl-diskless-sync yes # 无盘复制加速 RDB 传输
replica-lazy-flush no # 确保同步前快速清理旧数据
方案 B:一致性优先
# 适用于金融、账务等场景
WAIT 1 100 # 写入时确认至少 1 个副本成功
repl-backlog-size 512mb # 更大缓冲区容纳更多增量命令
常用调试命令
# 查看当前复制状态
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 故障的处理流程是什么?
- 运维人员收到告警(可能是半夜 3 点)
- 登录服务器,确认 Master 确实挂了
- 手动挑一个数据最新的 Replica,执行
SLAVEOF NO ONE提升为新 Master- 让其他 Replica 指向新 Master
- 通知客户端切换连接地址
整个过程可能需要 几分钟甚至更久,而且容易出错。Sentinel 要做的就是把这整套流程自动化。
架构组成
Sentinel 本身也是一个 Redis 进程(只是不处理普通数据读写),它的工作就是盯着 Master 和 Replica,必要时自动故障转移。
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 协同确认:
flowchart TD
A["Sentinel-1 向 Master 发 PING"] --> B{"收到 PONG?"}
B -->|"是"| C["一切正常, 继续监控"]
B -->|"否"| D["标记 Master 为 SDOWN<br/>(主观下线: 我觉得它挂了)"]
D --> E{"询问其他 Sentinel:<br/>你们觉得 Master 还活着吗?"}
E -->|"Quorum 个 Sentinel<br/>都认为挂了"| F["标记 Master 为 ODOWN<br/>(客观下线: 大家都确认它挂了)"]
E -->|"没达到 Quorum"| G["可能是网络局部问题<br/>保持 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 来统一操刀:
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 当选后,按以下顺序执行故障转移。整个过程通常在 几秒到几十秒 内完成:
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 <new-master-ip> <port>,它们开始从新 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 配置文件
# ── 核心配置:监控哪个 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 支持,原理是:
- 启动时向 Sentinel 查询当前 Master 地址
- 连接 Master 进行读写
- 如果连接失败,重新向 Sentinel 查询新地址
Go(go-redis)示例——客户端自动发现和切换:
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)示例:
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 看趋势):
# 复制状态 —— 最核心,异常时第一时间查这个
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
故障排查流程
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(如 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 在线才接受写入":
# 至少 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 中的部署示例