Redis 的高可用和高并发能力依赖于合理的集群架构设计。本文深入解析主从复制、哨兵模式和 Cluster 集群的原理与最佳实践。


一、Redis 集群架构演进

架构 特点 适用场景 数据分片 自动故障转移
单机 简单,但无高可用保障 开发/测试环境
主从复制 读写分离,手动故障转移 小规模,读多写少
哨兵模式 自动故障转移,监控主从状态 中等规模,需高可用
Cluster 数据分片,水平扩展,去中心化 大规模,高并发,需分片

二、主从复制 (Replication)

2.1 核心原理

主从复制: 将主节点(Master)的数据复制到从节点(Slave),实现读写分离

1
2
3
4
5
6
7
8
9
┌─────────┐  写操作
│ Master │ ◄────── 客户端
└────┬────┘
│ 数据同步
├──────────┬──────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Slave 1 │ │ Slave 2 │ │ Slave 3 │ ◄── 读操作
└─────────┘ └─────────┘ └─────────┘

2.2 复制流程

第一阶段:建立连接

  1. 从节点执行 REPLICAOF <masterip> <masterport>(或配置 replicaof)。
  2. 从节点与主节点建立 TCP 连接。
  3. 从节点发送 PING 命令,主节点回复 PONG(验证连通性)。

第二阶段:全量同步(Full Resync)

触发场景: 首次同步或从节点断线时间过长(增量缓冲区溢出)。

流程:

1
2
3
4
5
6
7
8
9
1. 从节点发送 PSYNC <runid> <offset>

2. 主节点执行 BGSAVE 生成 RDB 快照

3. 主节点将 RDB 文件发送给从节点

4. 从节点清空旧数据,加载 RDB 文件

5. 主节点将 BGSAVE 期间的新命令发送给从节点(backlog)

关键参数:

  • runid: 主节点的唯一标识(每次重启会变化)。
  • offset: 复制偏移量(标记同步进度)。

第三阶段:增量同步(Partial Resync)

触发场景: 从节点短暂断线重连。

流程:

1
2
3
4
5
6
7
1. 从节点发送 PSYNC <runid> <offset>

2. 主节点检查复制积压缓冲区(repl-backlog)

3. 如果 offset 在缓冲区范围内,发送增量命令

4. 如果不在,退化为全量同步

复制积压缓冲区(repl-backlog):

  • 环形缓冲区,默认 1MB。
  • 存储最近的写命令,用于断线重连时的增量同步。

配置:

1
2
repl-backlog-size 1mb      # 缓冲区大小
repl-backlog-ttl 3600 # 无从节点时,缓冲区保留时间(秒)

第四阶段:命令传播(Command Propagation)

正常运行时: 主节点将每条写命令实时传播给所有从节点。

心跳机制:

  • 从节点每秒发送一次 REPLCONF ACK <offset>(确认同步进度)。
  • 主节点检测从节点是否在线(超时则标记为下线)。

2.3 主从复制配置

从节点配置

1
2
3
4
5
6
7
8
9
# redis.conf(从节点)
replicaof 192.168.1.100 6379 # 指定主节点地址
masterauth mypassword # 主节点密码(如果有)

# 从节点只读(默认)
replica-read-only yes

# 从节点优先级(数值越小优先级越高,0 表示永不晋升为主节点)
replica-priority 100

主节点配置

1
2
3
4
5
6
# redis.conf(主节点)
# 最少 N 个从节点在线,主节点才接受写操作
min-replicas-to-write 1

# 从节点延迟超过 N 秒,主节点停止写操作
min-replicas-max-lag 10

2.4 主从复制的问题

问题 影响 解决方案
主节点宕机 需手动切换,服务中断 使用哨兵模式
全量同步阻塞 BGSAVE 导致性能抖动 使用 SSD、控制主节点数据量
从节点数量过多 主节点网络和 CPU 压力增大 使用级联复制(从-从结构)
复制延迟 从节点数据可能过期 监控 info replication 中的 lag

三、哨兵模式 (Sentinel)

3.1 核心功能

哨兵(Sentinel) 是 Redis 的高可用解决方案,提供以下功能:

  1. 监控(Monitoring): 检查主从节点是否正常工作。
  2. 通知(Notification): 通过 API 或脚本通知管理员。
  3. 自动故障转移(Automatic Failover): 主节点宕机时,自动选举新主节点。
  4. 配置中心: 客户端通过哨兵获取当前主节点地址。

3.2 架构设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌─────────────────────────────────────────┐
│ Sentinel 集群(至少 3 个) │
│ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │Sentinel│ │Sentinel│ │Sentinel│ │
│ │ 1 │ │ 2 │ │ 3 │ │
│ └───┬───┘ └───┬───┘ └───┬───┘ │
│ │ │ │ │
└──────┼──────────┼──────────┼────────────┘
│ │ │
└──────────┼──────────┘

┌────────▼────────┐
│ Master (主节点) │
└────────┬────────┘

┌───────┴───────┐
▼ ▼
┌────────┐ ┌────────┐
│ Slave 1 │ │ Slave 2 │
└────────┘ └────────┘

3.3 故障检测与选举

主观下线(Subjectively Down, SDOWN)

定义: 单个哨兵认为主节点下线(PING 超时)。

配置:

1
2
3
# sentinel.conf
sentinel monitor mymaster 192.168.1.100 6379 2 # 监控主节点
sentinel down-after-milliseconds mymaster 5000 # 5 秒超时判定下线

客观下线(Objectively Down, ODOWN)

定义: 多数哨兵(quorum)认为主节点下线。

quorum 配置:

1
2
sentinel monitor mymaster 192.168.1.100 6379 2
# 2 表示至少 2 个哨兵同意,主节点才被判定为客观下线

计算公式:

1
quorum = 哨兵总数 / 2 + 1(推荐配置)

选举新主节点(Leader Election)

流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1. 哨兵之间通过 Raft 协议选举出一个 Leader Sentinel

2. Leader Sentinel 从从节点中选出新主节点

选举规则(优先级递减):
├─ replica-priority 最小(非 0)
├─ 复制偏移量(offset)最大(数据最新)
└─ runid 最小(字典序)

3. Leader Sentinel 向新主节点发送 SLAVEOF NO ONE(晋升为主节点)

4. 其他从节点执行 REPLICAOF <new_master>(指向新主节点)

5. 更新哨兵配置,通知客户端新主节点地址

3.4 哨兵配置详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# sentinel.conf

# 监控主节点(名称、IP、端口、quorum)
sentinel monitor mymaster 192.168.1.100 6379 2

# 主节点密码
sentinel auth-pass mymaster mypassword

# 判定下线的超时时间(毫秒)
sentinel down-after-milliseconds mymaster 5000

# 故障转移超时时间(毫秒)
sentinel failover-timeout mymaster 180000

# 同步新主节点的从节点数量(1 表示逐个同步,避免雪崩)
sentinel parallel-syncs mymaster 1

# 脚本通知(故障转移时执行)
sentinel notification-script mymaster /var/redis/notify.sh
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

3.5 哨兵模式的优缺点

优点 缺点
自动故障转移,无需人工干预 写能力受限于单个主节点
监控主从节点健康状态 不支持数据分片(无法水平扩展)
客户端自动发现新主节点 配置复杂,运维成本高
高可用性(至少 3 个哨兵) 哨兵本身需要高可用(至少 3 个)

四、Redis Cluster (集群)

4.1 核心特性

Redis Cluster 是 Redis 官方的分布式解决方案,提供:

  1. 数据分片(Sharding): 数据自动分散到多个节点。
  2. 高可用(HA): 节点宕机自动故障转移。
  3. 去中心化: 无 Proxy 层,节点间直接通信。
  4. 线性扩展: 支持动态添加/删除节点。

4.2 哈希槽(Hash Slot)机制

核心概念: Redis Cluster 将数据空间划分为 16384 个哈希槽(0-16383)。

槽位分配

示例:3 主节点集群

1
2
3
Master A:槽 0-5460
Master B:槽 5461-10922
Master C:槽 10923-16383

键到槽位的映射

计算公式:

1
HASH_SLOT = CRC16(key) mod 16384

示例:

1
key1 → CRC16("key1") = 12345 → 12345 % 16384 = 12345 → Master C

Hash Tag(哈希标签)

作用: 强制多个 key 分配到同一槽位(支持多键操作)。

语法: {hashtag}key

示例:

1
2
MSET {user:1000}:name "Alice" {user:1000}:age 25
# {user:1000} 是 Hash Tag,确保两个 key 在同一槽位

4.3 节点间通信(Gossip 协议)

Gossip 协议: 节点间通过周期性的消息交换同步集群状态。

消息类型

消息类型 作用
MEET 新节点加入集群
PING 心跳检测(每秒随机向几个节点发送)
PONG 回复 PING
FAIL 标记节点下线
PUBLISH 发布订阅消息

集群总线端口

端口: Redis 集群使用 Redis端口 + 10000 作为集群总线端口。

示例:

1
2
节点 1:6379(客户端)、16379(集群总线)
节点 2:6380(客户端)、16380(集群总线)

4.4 故障检测与转移

主观下线(PFAIL)

定义: 节点 A 认为节点 B 下线(PING 超时)。

客观下线(FAIL)

定义: 多数主节点认为节点 B 下线。

流程:

1
2
3
4
5
6
7
1. 节点 A 发现节点 B 超时(PFAIL)

2. 节点 A 向其他节点广播:B 可能下线

3. 如果 超过半数主节点 同意,B 被标记为 FAIL

4. B 的从节点发起选举,晋升为新主节点

4.5 搭建 Redis Cluster

最小配置(3 主 3 从)

1
2
3
Master A (6379) ──┬── Slave A1 (6382)
Master B (6380) ──┼── Slave B1 (6383)
Master C (6381) ──┴── Slave C1 (6384)

配置文件

1
2
3
4
5
6
# redis-6379.conf
port 6379
cluster-enabled yes # 启用集群模式
cluster-config-file nodes-6379.conf # 集群配置文件
cluster-node-timeout 5000 # 节点超时时间(毫秒)
appendonly yes

创建集群

1
2
3
4
redis-cli --cluster create \
127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 \
127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 \
--cluster-replicas 1

参数说明:

  • --cluster-replicas 1:每个主节点有 1 个从节点。

4.6 集群扩容与缩容

扩容(添加节点)

1
2
3
4
5
6
7
8
# 1. 启动新节点
redis-server redis-6385.conf

# 2. 将新节点加入集群
redis-cli --cluster add-node 127.0.0.1:6385 127.0.0.1:6379

# 3. 重新分配槽位
redis-cli --cluster reshard 127.0.0.1:6379

缩容(删除节点)

1
2
3
4
5
# 1. 迁移槽位到其他节点
redis-cli --cluster reshard 127.0.0.1:6379

# 2. 删除节点
redis-cli --cluster del-node 127.0.0.1:6379 <node-id>

4.7 客户端路由(MOVED 与 ASK)

MOVED 重定向

场景: 客户端访问错误节点。

流程:

1
2
3
Client → Node A (槽 1234 不在此节点)
Node A 返回:MOVED 1234 127.0.0.1:6380
Client → Node B (正确节点)

ASK 重定向

场景: 槽位正在迁移中。

流程:

1
2
3
4
Client → Node A (槽 1234 正在迁移到 Node B)
Node A 返回:ASK 1234 127.0.0.1:6380
Client → Node B 发送 ASKING 命令
Client → Node B 执行操作

4.8 Cluster 的限制

限制 说明 解决方案
不支持多键操作 MGET key1 key2(不同槽位) 使用 Hash Tag
不支持多数据库 Cluster 模式只有 db0 业务层隔离
事务支持有限 事务中的 key 必须在同一槽位 使用 Hash Tag 或 Lua 脚本
不支持发布订阅的跨节点 PUBLISH 只在当前节点生效 使用消息队列(Kafka、RabbitMQ)
从节点不参与读操作(默认) 从节点仅用于高可用 开启 READONLY 模式

4.9 Cluster vs 哨兵对比

特性 哨兵模式 Cluster
数据分片 ❌(单主节点,写能力受限) ✅(16384 个槽位,水平扩展)
自动故障转移
配置复杂度 中等
客户端支持 需哨兵感知客户端 需集群感知客户端(如 Jedis、Lettuce)
适用场景 中等规模,读多写少 大规模,高并发,需分片

五、集群选型建议

场景 推荐方案 理由
小规模,读多写少 主从复制 + 哨兵 简单,成本低
中等规模,需高可用 哨兵模式 自动故障转移,配置相对简单
大规模,高并发,需分片 Redis Cluster 水平扩展,数据分片,去中心化
超大规模(TB 级数据) Cluster + Proxy Twemproxy/Codis 实现分片路由
多云/混合云部署 哨兵 + VIP VIP 漂移实现跨机房故障转移

六、面试高频问题

  1. 主从复制的全量同步和增量同步有什么区别?

    • 全量同步:RDB + backlog;增量同步:仅发送断线期间的命令。
  2. 哨兵是如何选举出新主节点的?

    • Raft 协议选举 Leader Sentinel,再根据优先级、offset、runid 选出新主节点。
  3. Redis Cluster 如何实现数据分片?

    • 通过 CRC16(key) % 16384 计算槽位,每个节点负责一部分槽位。
  4. Cluster 的 MOVED 和 ASK 重定向有什么区别?

    • MOVED:槽位固定不在此节点;ASK:槽位正在迁移中。
  5. 如何解决 Cluster 的多键操作限制?

    • 使用 Hash Tag 强制多个 key 在同一槽位。
  6. 为什么哨兵至少需要 3 个?

    • 保证 quorum(多数派)有效,避免脑裂。
  7. Cluster 如何保证高可用?

    • 每个主节点有从节点,主节点宕机时自动故障转移。

通过合理的集群架构设计,Redis 可以支撑亿级流量和 PB 级数据!🚀