Skip to content

集群模式详解

  Redis Cluster 是 Redis 官方提供的分布式解决方案,解决了单机内存上限、单机 QPS 上限、单点故障三大问题。相比哨兵模式的主从架构,集群模式真正实现了数据分片和水平扩展。

一、为什么需要集群?

哨兵模式的局限:

  ┌──────────────────────────────────────────────────────┐
  │  哨兵主从架构                                         │
  │                                                      │
  │  ┌──────────────┐                                    │
  │  │   Master      │  ← 所有数据都在这里!               │
  │  │  (全量数据)   │     内存上限 = 单机内存             │
  │  └───┬────┬─────┘     QPS 上限 = 单机 QPS             │
  │      │    │                                           │
  │      ▼    ▼                                           │
  │  ┌──────┐ ┌──────┐                                   │
  │  │Slave1│ │Slave2│  ← 只读,不分担写压力               │
  │  └──────┘ └──────┘                                   │
  │                                                      │
  │  问题:❌ 单机内存上限(256GB 到头)                    │
  │        ❌ 单机 QPS 上限(10W 左右)                     │
  │        ❌ 写压力无法分担                                │
  └──────────────────────────────────────────────────────┘

集群模式:

  ┌──────────────────────────────────────────────────────┐
  │  Redis Cluster(3 主 3 从)                           │
  │                                                      │
  │  ┌──────────┐  ┌──────────┐  ┌──────────┐            │
  │  │ Master 1 │  │ Master 2 │  │ Master 3 │            │
  │  │ Slot 0-  │  │ Slot     │  │ Slot     │            │
  │  │ 5460     │  │ 5461-    │  │ 10923-   │            │
  │  │          │  │ 10922    │  │ 16383    │            │
  │  └────┬─────┘  └────┬─────┘  └────┬─────┘            │
  │       │             │             │                   │
  │       ▼             ▼             ▼                   │
  │  ┌──────────┐  ┌──────────┐  ┌──────────┐            │
  │  │ Slave 1  │  │ Slave 2  │  │ Slave 3  │            │
  │  └──────────┘  └──────────┘  └──────────┘            │
  │                                                      │
  │  ✅ 数据分片:每个 Master 存 1/3 数据                   │
  │  ✅ 水平扩展:加 Master 自动重新分配数据                 │
  │  ✅ 读写分离:每个 Master 可写,Slave 可读              │
  │  ✅ 自动故障转移:Master 挂了,Slave 自动提升           │
  └──────────────────────────────────────────────────────┘

二、数据分区方案

方案一:节点取余分区

hash(key) % N  → 确定节点

示例(3 个节点):
  key "user:1" → hash("user:1") = 100 → 100 % 3 = 1 → 节点 1
  key "user:2" → hash("user:2") = 200 → 200 % 3 = 2 → 节点 2
  key "user:3" → hash("user:3") = 300 → 300 % 3 = 0 → 节点 0

问题:扩容时大量数据迁移
  3 节点 → 4 节点
  hash("user:1") % 3 = 1  →  hash("user:1") % 4 = 0
  hash("user:2") % 3 = 2  →  hash("user:2") % 4 = 0
  hash("user:3") % 3 = 0  →  hash("user:3") % 4 = 0
  几乎所有 key 分布都变了!需要全量迁移

方案二:一致性哈希分区

环形哈希空间(0 ~ 2^32-1):

                 0
        ┌───────┴───────┐
        │               │
        │    ┌─────┐    │
        │    │Node1│    │
    Node3─────┘   └────Node2
        │               │
        └───────┬───────┘
             2^32-1

  key → hash(key) → 在环上顺时针找第一个节点
  扩容时只需迁移相邻节点的一部分数据

  问题:节点少时分布不均,容易产生热点
  解决:虚拟节点(每个物理节点映射多个虚拟节点)

方案三:虚拟槽分区(Redis Cluster 采用)

Redis Cluster 将数据空间分为 16384 个槽(Slot):

  ┌──────────────────────────────────────────────────────┐
  │  0 ──────────────────────────────────────── 16383    │
  │                                                      │
  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐│
  │  │   Master 1   │  │   Master 2   │  │   Master 3   ││
  │  │  0 ~ 5460    │  │  5461 ~ 10922│  │  10923 ~16383││
  │  └──────────────┘  └──────────────┘  └──────────────┘│
  └──────────────────────────────────────────────────────┘

  key → CRC16(key) % 16384 → slot → 查映射表 → 节点

  优势:
  ✅ 槽是数据迁移的最小单位,灵活可控
  ✅ 节点扩容时,只需迁移部分槽
  ✅ 槽数量适中(16384),心跳包中可用位图表示

为什么是 16384 个槽?

16384(16KB)是心跳包中槽位图的大小。如果 65536 个槽,心跳包会达到 8KB,频繁发送消耗带宽。16384 在大多数情况下足够分配,且心跳包较小。

三、集群搭建

3.1 配置

conf
# redis.conf
cluster-enabled yes              # 开启集群模式
cluster-config-file nodes.conf   # 集群节点信息文件
cluster-node-timeout 15000       # 节点超时(毫秒)
cluster-require-full-coverage yes # 所有槽都有节点才提供服务

3.2 创建集群

bash
# 启动 6 个 Redis 实例(3 主 3 从)
redis-server redis-7000.conf
redis-server redis-7001.conf
redis-server redis-7002.conf
redis-server redis-7003.conf
redis-server redis-7004.conf
redis-server redis-7005.conf

# 创建集群(Redis 5.0+)
redis-cli --cluster create \
  192.168.1.100:7000 192.168.1.100:7001 \
  192.168.1.100:7002 192.168.1.100:7003 \
  192.168.1.100:7004 192.168.1.100:7005 \
  --cluster-replicas 1

3.3 客户端连接

bash
# -c 启用集群模式,自动重定向
redis-cli -c -p 7000

> SET key1 value1
 Redirected to slot [9189] located at 192.168.1.100:7002
OK

四、集群原理

4.1 Gossip 协议

Redis Cluster 节点之间通过 Gossip 协议(流言协议)通信,类似"一传十,十传百"。每个节点定期随机选择几个节点发送 PING/PONG 消息,交换集群状态信息。

Gossip 消息类型:

  MEET   → 新节点加入集群("我是新节点,认识一下")
  PING   → 定期发送心跳("我还活着,这些是我知道的节点信息")
  PONG   → 回复 PING/MEET("收到,这是我的信息")
  FAIL   → 标记节点下线("节点 X 挂了,大家注意")

  每个节点通过 Gossip 最终掌握整个集群的完整状态

4.2 故障检测与转移

故障检测:

  1. 节点 A 向节点 B 发送 PING,超时未收到 PONG
  2. 节点 A 标记节点 B 为 PFAIL(疑似下线)
  3. 节点 A 通过 Gossip 传播 PFAIL 信息
  4. 当半数以上主节点都认为 B 是 PFAIL
  5. 标记为 FAIL(确认下线)并广播

故障转移(自动):

  1. B 的 Slave 发现 Master 已 FAIL
  2. Slave 等待 cluster-node-timeout 的 2 倍时间
  3. 向其他 Master 发起选举投票
  4. 获得多数票 → 提升为新 Master
  5. 接管原 Master 的槽位

4.3 MOVED 与 ASK 重定向

MOVED 重定向(槽已永久迁移):

  客户端 → 节点 1:GET key1
  节点 1 → 客户端:MOVED 9189 192.168.1.100:7002
  客户端 → 节点 2:GET key1  (自动重定向)
  节点 2 → 客户端:value1

ASK 重定向(槽正在迁移中):

  客户端 → 节点 1:GET key1
  节点 1 → 客户端:ASK 9189 192.168.1.100:7002
  客户端 → 节点 2:ASKING + GET key1
  节点 2 → 客户端:value1

  区别:
  MOVED:永久,客户端更新槽映射表
  ASK:临时,仅本次有效,不更新映射表

五、集群伸缩

5.1 扩容(添加节点)

bash
# 1. 启动新节点
redis-server redis-7006.conf

# 2. 加入集群
redis-cli --cluster add-node 192.168.1.100:7006 192.168.1.100:7000

# 3. 重新分配槽位
redis-cli --cluster reshard 192.168.1.100:7000
# 输入要迁移的槽数量:4096
# 输入目标节点 ID:新节点 7006 的 ID

# 4. 添加 Slave
redis-cli --cluster add-node 192.168.1.100:7007 192.168.1.100:7000 \
  --cluster-slave --cluster-master-id 新Master的ID

5.2 缩容(删除节点)

bash
# 1. 迁移槽位到其他节点
redis-cli --cluster reshard 192.168.1.100:7000
# 指定源节点和目标节点

# 2. 删除节点
redis-cli --cluster del-node 192.168.1.100:7000 节点ID

六、集群限制

集群模式下不支持的命令:

  ❌ 多 key 操作(跨槽):MGET key1 key2(如果 key1 和 key2 不同槽)
  ❌ 事务(跨槽):MULTI/EXEC 中的 key 必须在同一槽
  ❌ Lua 脚本(跨槽):脚本中的 key 必须在同一槽

  解决:使用 Hash Tag 强制 key 落在同一槽
  → MGET {user}:1 {user}:2
  → CRC16("user") 决定槽,而非整个 key

七、面试要点

Q1:Redis Cluster 的数据分区方案是什么?

  虚拟槽分区,将数据空间分为 16384 个槽,每个 Master 负责一部分槽。key 通过 CRC16(key) % 16384 定位到槽,再通过槽映射表找到对应节点。

Q2:Gossip 协议是什么?

  分布式节点间通信协议,每个节点定期随机选择几个节点交换信息,最终所有节点都掌握全集群状态。优点是去中心化、容错性好,缺点是收敛较慢。

Q3:集群故障转移的原理?

  PFAIL(疑似下线)→ 半数以上主节点确认 → FAIL(确认下线)→ Slave 发起选举 → 获得多数票 → 提升为新 Master → 接管槽位。

Q4:MOVED 和 ASK 重定向的区别?

  MOVED 表示槽已永久迁移,客户端应更新路由表;ASK 表示槽正在迁移中,是临时重定向,客户端不更新路由表。

Q5:集群为什么是 16384 个槽?

  16384 个槽的心跳位图恰好 16KB,在心跳包大小和槽分配灵活度之间取得平衡。65536 个槽会导致心跳包过大,影响带宽。