2026年7月12日 · 6 分钟阅读
Redis 高可用架构:主从复制、哨兵与分片集群
深入 Redis 主从同步的 replid/offset 机制、哨兵的主观/客观下线判定与故障转移流程、分片集群的 16384 个哈希槽路由原理与集群通信模型。
单机 Redis 有三个瓶颈:单点故障、海量数据存不下、写请求扛不住。
围绕这三个问题,Redis 提供了三层高可用方案:
主从复制 → 哨兵 → 分片集群
每一层解决不同的痛点。从底层原理看,它们不是三个独立的机制,而是一层一层叠加的。
主从复制
为什么需要主从
- 高可用:一台 Redis 挂了,还有另一台接着提供服务。
- 读写分离:master 扛写,slave 扛读,提升吞吐量。
- 数据备份:在 slave 上做持久化,不影响 master。
全量同步
slave 第一次连接到 master 时,触发全量同步:
slave → slaveof master_ip master_port
→ master 执行 bgsave 生成 RDB 快照
→ master 将 RDB 文件发送给 slave
→ slave 清空自身数据,加载 RDB
→ master 将 RDB 生成期间的增量写命令(缓冲区)发送给 slave
→ slave 执行增量命令
replid 和 offset
全量同步之后,怎么知道后续是否需要同步?
Redis 主从之间通过两个标识来判断数据一致性:
- replication ID(replid):数据集的”姓氏”(标记)。每个 master 有唯一的 replid,slave 继承 master 的 replid。
- offset:偏移量。每条写入命令在复制积压缓冲区(repl_backlog)中都有位置。
if slave.replid == master.replid and slave.offset < master.offset:
# 可以增量同步
send_commands(master.repl_backlog[slave.offset : master.offset])
else:
# replid 变了,或者 offset 对不上 → 全量重同步
full_sync()
增量同步
全量同步之后,master 的写命令会同时写入repl_backlog(一个固定大小的环形缓冲区)。slave 定期把自己的 offset 发给 master,master 把 backlog 中缺失的部分发给 slave。
repl_backlog 大小默认 1MB。如果 slave 断连太久,offset 指向的数据在 backlog 中被覆盖了,就只能再次全量同步。
优化建议
- 启用无磁盘复制:
repl-diskless-sync yes,master 直接将 RDB 数据通过 socket 发给 slave,避免磁盘 IO。 - 单节点内存不要太大(≤ 8G),减少 fork 和 RDB 传输压力。
- 适当增大
repl-backlog-size,让 slave 有更长的断连容忍时间。 - 主-从-从链式结构:一个 master 挂太多 slave 会消耗大量网络带宽,形成级联。
哨兵(Sentinel)
主从复制解决了”数据有备份”,但没解决”master 挂了怎么办”。哨兵就是用来做自动故障转移的。
三个职责
- 监控(Monitoring):每秒向所有节点发送 ping,检查是否存活。
- 自动故障转移(Failover):master 宕机时,选择一个 slave 提升为 master。
- 通知(Notification):将最新主从信息推送给客户端。
心跳与下线判定
哨兵集群之间独立决策,通过投票达成共识:
- 主观下线(SDOWN):单个哨兵发现一个实例在规定时间内没响应 ping。
- 客观下线(ODOWN):超过
quorum数量的哨兵都认为该实例主观下线,才判定客观下线。
quorum 建议超过哨兵总数的一半(例如 3 个哨兵设 quorum = 2)。
故障转移流程
当 master 被判定客观下线:
1. 哨兵集群选出一个 leader 执行故障转移
2. leader 选择新的 master(选优规则如下)
3. 向备选 slave 发送 slaveof no one,提升为 master
4. 向其他 slave 发送 slaveof new_master_ip new_master_port
5. 将原 master 标记为 slave,恢复后自动成为新 master 的从节点
新 master 选择规则
1. 排除断线超过 down-after-milliseconds × 10 的 slave
2. 选择 slave-priority 最小的(0 表示永不参与选举)
3. 若优先级相同,选 offset 最大的(数据最新)
4. 若 offset 也相同,选 run_id 最小的
客户端读取策略
| 策略 | 说明 |
|---|---|
MASTER | 只从主节点读 |
MASTER_PREFERRED | 优先主节点,主不可用时读从 |
REPLICA | 只从从节点读 |
REPLICA_PREFERRED | 优先从节点,从不可用时读主(推荐) |
分片集群
主从 + 哨兵解决了高可用和高并发读,但海量数据存储和高并发写这两个问题还没解决——你再怎么加 slave,单个 master 的内存和写能力是有上限的。
分片集群(Redis Cluster)就是要解决这两个问题。
哈希槽(Hash Slot)
Redis Cluster 把整个 key 空间划分为 16384 个哈希槽:
slot = CRC16(key) % 16384
这 16384 个槽被平均分配给集群中的 master 节点:
3 个 master:每个节点 ≈ 5461 个槽
6 个 master:每个节点 ≈ 2730 个槽
key 不是直接绑定到节点,而是绑定到槽。所以新增或删除节点时,只需要迁移槽(连带该槽中的 key),不需要整体 rehash。
分片路由
客户端请求任意节点:
→ 节点计算 key 的 slot
→ 如果 slot 在自己名下,直接处理
→ 否则返回 MOVED 错误,告诉客户端正确的节点
Redis 客户端(如 JedisCluster、Lettuce)会在本地缓存槽到节点的映射,尽量避免重定向。
请求合并
如果批量操作的 key 不在同一个 slot 上,Redis Cluster 会直接报错。这是为什么集群下批量操作要使用 hash tag来保证 key 落在同一槽:
user:{123}:name → slot = CRC16("123") % 16384
user:{123}:email → slot = CRC16("123") % 16384 // 同一个槽!
hash tag 规则:key 中第一个 {} 包含的内容作为有效部分计算 slot。
节点间通信
集群中的 master 之间通过 Gossip 协议 相互 ping 来检测健康状态。每次 ping 至少包含:
- 发送者的槽信息
- 发送者视角的集群状态
- 其他节点的信息(随机的 2-3 个)
一个 10 节点的集群,每次 ping 携带的信息大约 1KB。节点数越多,集群内网络开销越大。建议集群节点数不超过 1000。
手动故障转移
CLUSTER FAILOVER 命令让 slave 触发手动切换:
1. slave 向 master 发起同步请求,确保数据一致
2. master 停止处理写入,将复制偏移量同步给 slave
3. slave 完成同步,发起故障转移
4. 集群感知新 master 上线
支持三种模式:
| 模式 | 说明 |
|---|---|
| 缺省 | 校验 offset 一致性,确保数据不丢 |
force | 跳过 offset 校验 |
takeover | 直接接管,忽略所有一致性检查(极端情况) |
节点配置建议
- 节点数量不要超过 1000(gossip 协议流量随节点数平方级增长)。
- 避免在同一物理机上运行过多 Redis 实例。
- 配置合适的
cluster-node-timeout(默认 15000ms):节点超时判定时间,影响故障转移速度。 cluster-require-full-coverage yes(默认):如果有任意 slot 不可用,整个集群停止服务。生产环境建议根据业务容忍度决定是否改no。
三种方案对比
| 对比维度 | 主从复制 | 哨兵 | 分片集群 |
|---|---|---|---|
| 解决的核心问题 | 数据备份、读性能 | 自动故障切换 | 海量数据存储、高并发写 |
| 数据分片 | 不支持 | 不支持 | 支持(16384 槽) |
| 自动故障转移 | 不支持(手动切换) | 支持 | 支持 |
| 复杂度 | 低 | 中 | 高 |
| 节点数上限 | 少量 | 少量 | ≤ 1000 |
使用场景建议:
- 数据量小需要高可用 → 主从 + 哨兵。
- 数据量大需要水平扩展 → 分片集群。
- 读多写少且能容忍写性能瓶颈 → 主从 + 哨兵 + 读写分离。
- 写频繁且数据量大 → 分片集群 + 每个分片再配置哨兵或主从。
总结
- replid + offset 是主从同步的”身份证”,决定了全量还是增量同步。
- 主观下线 + 客观下线 是哨兵的共识机制,quorum 决定了容错边界。
- 16384 个哈希槽 是分片集群的路由基础,槽绑定数据而非绑定节点,让扩缩容的迁移量可控。
- Redis Cluster 的 gossip 协议在节点数较少时工作良好,但超过 1000 个节点后性能开始下降——你需要拆分多个集群。