Appearance
集群模式详解
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 13.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的ID5.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 个槽会导致心跳包过大,影响带宽。
