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

13 KiB
Raw Blame History

tags, create time
tags create time
Redis
缓存
高可用
主从
Sentinel
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 的前置知识
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 确认。这保证了极致低延迟,代价是极端情况下可能丢失数据:

场景:  写 A=1 → Master 返回 OK → Master 宕机(还没传到 Replica)→ 重启后 A 不存在

若业务需要 至少一份副本落盘 的强一致性保证,可配合 Lua + wait 命令实现弱半同步:

// 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,核心优势:断线重连后可增量同步。

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

flowchart LR
    WriteHead["写入指针"] -->|"新命令流入"| Buffer["repl-backlog-buffer<br/>环形缓冲区(默认1MB)"]
    ReadHead["读取指针"] -->|"命令回放给 Replica"--> Buffer
    Buffer -->|"溢出"`覆盖最旧数据`"
# 相关配置
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?

redis-cli --bigkeys          # 在线扫描(谨慎!高 QPS 环境有性能影响)
scan 0 MATCH * COUNT 100000 # 逐页扫描,用 type/hashlist/set 分别评估

解决方案:

  • 写入侧:分拆大 key 为多个小 key(如按用户 ID 哈希分散)
  • 同步侧:增大 repl-diskless-sync-delay 让多个 Replica 同时接收,避免重复传输

二、级联复制(拓扑优化)

当 Replica 数量增多时,每个都连 Master 会造成带宽压力:

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 负担。

三、只读副本注意事项

replica-read-only yes    # 默认值

[!WARNING] 禁止在 Replica 上写操作 即使关闭 read-only,Replica 上的写入在主从重新同步时会被清掉。而且 DEL 一个不存在的 key 也会触发额外命令发送给 Master。

Replica 的其他关键配置

# --- 副本如何发现 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:极致性能优先

# 适用于读 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 哨兵机制

架构组成

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)

# 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)

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 执行以下流程:

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 配置文件

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-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")
# 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 策略应相同

监控项

# 关键指标(每秒采样)
INFO replication          # connected_slaves, master_repl_offset
INFO memory               # used_memory_human (同步期间会突增)
INFO stats                # instantaneous_ops_per_sec

故障排查流程

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] 防脑裂配置

# Master 端配置:至少 N 个 Replica 在线才接受写入
min-replicas-to-write 1
# 对应的最大允许延迟(秒),超时视为掉线
min-replicas-max-lag 10

六、局限性与替代方案

问题 Sentinel 无法解决
单 Master 写入瓶颈 ❌ 只有一个 Master
数据量超限单机容量 ❌
跨机房容灾 ❌(Sentinel 跨网段延迟太高)

[!TIP] 需要水平扩展?看 Cluster 方案 → hhs/Redis/07-集群方案

关联笔记