Tip
- 主从复制解决数据备份和读扩展,哨兵在此之上解决“主挂了怎么办”。
- 没有哨兵,主节点宕机后需要人工手动切换;有了哨兵,整个过程自动化完成
部署架构:
- 1 主 + N 从 + M 个哨兵(M 通常为 3 或 5 的奇数)
- 哨兵节点必须分布在不同的物理机或虚拟机上,避免单机故障导致整个哨兵集群失效
1 主从
Note
- 主节点(master)负责写,从节点(slave)通过复制主节点的数据变更来保持同步。从节点默认只读(
replica-read-only yes),可以分担读请求。- 首次全量同步(RDB)+ 后续增量命令传播,断线时通过 backlog + offset 尽量增量同步
- 异步,不保证强一致,只保证最终一致
1.1 立复制关系
- 配置文件:
replicaof <master-ip> <master-port> - 启动命令:
redis-server --replicaof <master-ip> <master-port> - 运行时命令:
REPLICAOF <master-ip> <master-port>(Redis 5.0 前是SLAVEOF)
从节点执行后会连接主节点,发送 PSYNC 命令请求同步。
1.2 全量同步(第一次连接或无法增量时)
1.2.1 从节点发送 PSYNC
PSYNC ? -1? 表示不知道主节点的 runid,-1 表示没有偏移量。这是首次连接的标志。
1.2.2 主节点回复 FULLRESYNC
+FULLRESYNC <runid> <offset>告诉从节点自己的 runid 和当前复制偏移量。
1.2.3 主节点执行 BGSAVE 生成 RDB
主节点 fork 子进程生成 RDB 快照文件。在生成期间的新写命令会被存入 replication buffer(每个从节点一个)。
1.2.4 发送 RDB 给从节点
主节点把 RDB 文件通过网络发给从节点。从节点先清空自己的数据,再加载 RDB。
1.2.5 发送缓冲区中的增量命令
RDB 发送完后,主节点把 replication buffer 中积累的写命令发给从节点。从节点执行这些命令,追上主节点。
1.2.6 进入命令传播阶段
之后主节点每执行一条写命令,就异步发给所有从节点,保持持续同步。
1.3 增量同步(断线重连时)
如果从节点只是短暂断线,重新连接时不想再全量同步,就用增量同步。
前提条件
- 主节点的 replication backlog(复制积压缓冲区)中还保留着从节点缺失的那部分数据
- 从节点提供的 runid 和 offset 匹配
流程:
从节点: PSYNC <runid> <offset>
主节点: +CONTINUE主节点从 backlog 中找到 offset 之后的数据,发给从节点。
replication backlog 是什么?
- 一个固定大小的环形缓冲区(默认 1MB,
repl-backlog-size可配) - 主节点在命令传播阶段,除了发给从节点,还会写入 backlog
- 它是所有从节点共享的
- 如果从节点断线太久,缺失的数据已被覆盖,就只能全量同步
backlog 大小怎么估算?
backlog = 平均写入速率 × 最长断线时间比如每秒写 1MB,允许断线 60 秒,就该配 60MB。
1.4 命令传播阶段
进入稳定状态后,主节点每执行一条写命令,会:
- 写入自己的内存
- 追加到 AOF(如果开启)
- 发送给所有从节点
- 写入 replication backlog
从节点收到命令后执行,并异步向主节点回复偏移量(REPLCONF ACK <offset>)。主节点通过这个 ACK 知道从节点的同步进度。
心跳机制:
- 主节点每秒向从节点发
PING - 从节点每秒向主节点发
REPLCONF ACK <offset>
为什么从节点是异步复制?
主节点不会等从节点确认就返回给客户端,所以主从之间是异步的。这意味着主节点宕机时,可能有一部分数据还没同步到从节点,导致数据丢失。这是 Redis 主从复制的固有特性。
1.5 复制偏移量与 runid
1.5.1 复制偏移量(offset)
- 主节点和从节点各自维护一个 offset
- 主节点每发送 N 字节数据,offset 增加 N
- 从节点每接收 N 字节,offset 增加 N
- 通过比较 offset 判断是否同步
1.5.2 runid
- 每个 Redis 实例启动时生成的 40 位随机字符串
- 从节点保存主节点的 runid
- 如果主节点重启,runid 会变(除非用
debug reload),从节点发现 runid 不匹配就全量同步
1.6 全量同步 vs 增量同步对比
| 维度 | 全量同步 | 增量同步 |
|---|---|---|
| 触发时机 | 首次连接、runid 不匹配、backlog 覆盖 | 短暂断线重连 |
| 数据传输 | RDB 全量文件 | backlog 中的增量命令 |
| 开销 | 大(fork + 网络 + 从节点清空重载) | 小 |
| 命令 | PSYNC ? -1 | PSYNC <runid> <offset> |
| 主节点回复 | +FULLRESYNC | +CONTINUE |
2 哨兵
2.1 核心定时任务
-
每 10 秒发送 INFO 命令
每个哨兵向主节点和从节点发送INFO命令,获取最新拓扑结构。哨兵借此自动发现从节点,无需在配置中手动列出所有从节点地址 -
每 2 秒发布/订阅消息
哨兵通过主节点的_sentinel_:hello频道发布自己的信息,同时订阅该频道来发现其他哨兵并交换对主节点状态的判断。这是哨兵之间互相感知和达成共识的机制 -
每 1 秒发送 PING 心跳
哨兵向所有主从节点及其他哨兵发送PING,检测它们是否可达。这是故障判定的数据来源
2.2 故障判定
示例:
sentinel monitor mymaster 127.0.0.1 6379 2 中的 2 就是 quorum,表示至少 2 个哨兵同意才能判定主节点故障
2.2.1 主观下线
单个哨兵在 down-after-milliseconds 时间内未收到有效回复,就单方面认为该节点不可用。这只是一家之言,可能是网络抖动
2.2.2 客观下线
当主节点被某哨兵标记为主观下线后,该哨兵会向其他哨兵发送 SENTINEL is-master-down-by-addr 询问。如果达到 quorum 数量的哨兵(包括自己)都确认主节点不可达,才判定为客观下线,触发故障转移
2.3 领导者选举
判定客观下线后,需要选出一个领导者哨兵来执行故障转移,避免多个哨兵同时操作导致混乱
选举基于 Raft 算法的变体:每个认为主节点下线的哨兵都可以向其他哨兵拉票,收到请求的哨兵在同一纪元(epoch)内只投给第一个请求者。获得超过半数且不少于 quorum 票数的哨兵成为领导者
2.4 选择新主节点
领导者哨兵按以下优先级从从节点中选出新主:
- 网络连接状态良好的从节点(排除已断开的)
- 优先级最高的(
slave-priority配置值,越小优先级越高) - 复制偏移量最大的(数据最完整,最接近原主节点)
- 运行 ID 最小的(字典序,兜底规则)
2.5 故障转移执行
选出新主后,领导者哨兵执行四步操作:
- 向选中的从节点发送
SLAVEOF NO ONE,将其提升为主节点 - 向其他从节点发送
SLAVEOF命令,让它们复制新主节点 - 将旧主节点(如果恢复)也配置为新主节点的从节点
- 通过 Pub/Sub 通知所有哨兵更新配置,客户端通过
SENTINEL get-master-addr-by-name获取新主地址
2.6 客户端
客户端不能直接硬编码主节点地址,而应该连接哨兵集群来发现当前主节点
流程:
- 客户端连接任意一个哨兵,执行
SENTINEL get-master-addr-by-name mymaster - 获得主节点地址后,调用
ROLE命令验证该实例确实是主节点 - 发生连接错误时,重新向哨兵查询地址
2.7 脑裂
指网络分区时出现多个“主节点”同时接受写入,导致数据不一致
防护措施:
- 部署奇数个哨兵(3 或 5),避免投票平手
- 配置合理的
quorum值 - 在 Redis 主节点上配置
min-slaves-to-write和min-slaves-max-lag,限制主节点在从节点失联时拒绝写入
关键约束
无论 quorum 设为多少,故障转移必须获得多数哨兵(majority) 的支持才能执行。只有少数哨兵存活时,无法完成自动故障转移