引言
单个Redis实例内存有限(通常16-64GB),如何存储TB级数据?如何实现水平扩展? Redis Cluster提供了原生的分布式解决方案。
一、Cluster概述
1.1 为什么需要Cluster?
主从复制+哨兵的局限:
主从架构:
┌──────────┐
│ Master │ (单节点存储所有数据)
└────┬─────┘
│ 复制
┌────┴────┬────────┐
│ Slave1 │ Slave2 │ (仅读扩展,不能写扩展)
└─────────┴────────┘
问题:
❌ 单点容量瓶颈(受限于单机内存)
❌ 写性能无法扩展(所有写操作都在主节点)
❌ 主节点故障影响所有数据访问
Cluster分片架构:
Cluster:
┌───────┐ ┌───────┐ ┌───────┐
│Master1│ │Master2│ │Master3│ (数据分片存储)
│Slave1 │ │Slave2 │ │Slave3 │
└───────┘ └───────┘ └───────┘
↓ ↓ ↓
存储1/3 存储1/3 存储1/3
数据 数据 数据
优势:
✅ 容量扩展(添加节点)
✅ 写性能扩展(多个主节点)
✅ 高可用(每个主节点有从节点)
1.2 核心概念
- 分片(Sharding):数据分散存储在多个节点
- 槽位(Slot):16384个槽位,分配给不同节点
- 节点(Node):独立的Redis实例
- 主从复制:每个主节点可有多个从节点
二、槽位机制
2.1 什么是槽位?
Redis Cluster共有16384个槽位(0-16383):
槽位分配示例(3个主节点):
Master1:0-5460 (5461个槽位)
Master2:5461-10922 (5461个槽位)
Master3:10923-16383 (5461个槽位)
2.2 key到槽位的映射
计算公式:
slot = CRC16(key) % 16384
示例:
key="user:1001"
CRC16("user:1001") = 51234
slot = 51234 % 16384 = 1698
查找:slot 1698在Master1上 → 访问Master1
哈希标签(Hash Tag):
// 确保多个key在同一个节点
jedis.set("{user:1001}:name", "Alice");
jedis.set("{user:1001}:age", "25");
// {user:1001}部分用于计算slot
// 两个key都在同一个节点上
2.3 槽位分配
手动分配:
# 添加节点
redis-cli --cluster add-node 127.0.0.1:7000 127.0.0.1:7001
# 分配槽位
redis-cli --cluster reshard 127.0.0.1:7000
# 提示:How many slots? 5461
# 提示:What is the receiving node ID? <node-id>
# 提示:Source node? all
自动分配:
# 创建集群(自动分配槽位)
redis-cli --cluster create \
127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 \
--cluster-replicas 1
三、节点通信
3.1 Gossip协议
每个节点维护集群元数据:
- 所有节点列表
- 槽位分配信息
- 节点状态(在线/下线)
通信方式:
节点1 → 节点2:发送PING(携带部分元数据)
节点2 → 节点1:回复PONG(携带部分元数据)
效果:
- 最终一致性(元数据最终在所有节点同步)
- 去中心化(无需配置中心)
3.2 节点发现
新节点加入:
1. redis-cli --cluster add-node <new-node> <existing-node>
2. 新节点与集群中任一节点建立连接
3. 通过Gossip协议,新节点发现所有其他节点
4. 所有节点发现新节点
3.3 故障检测
节点1定期向所有节点发送PING:
↓
节点2超时无响应(超过cluster-node-timeout)
↓
节点1标记节点2为PFAIL(疑似下线)
↓
节点1通过Gossip通知其他节点
↓
多数主节点也认为节点2下线
↓
标记节点2为FAIL(确认下线)
↓
触发故障转移
四、客户端重定向
4.1 MOVED重定向
场景:key不在当前节点
客户端 → 节点1:GET user:1001
↓
计算slot=1698,发现slot在节点2
↓
节点1 → 客户端:MOVED 1698 127.0.0.1:7001
↓
客户端 → 节点2:GET user:1001
↓
节点2 → 客户端:返回value
客户端优化:
- 缓存槽位分配信息
- 直接访问目标节点(避免重定向)
4.2 ASK重定向
场景:槽位正在迁移
槽位1698正在从节点1迁移到节点2:
↓
客户端 → 节点1:GET user:1001
↓
节点1查找:key已迁移 → 返回ASK 1698 127.0.0.1:7002
↓
客户端 → 节点2:ASKING + GET user:1001
↓
节点2 → 客户端:返回value
MOVED vs ASK:
- MOVED:槽位永久分配给其他节点
- ASK:槽位临时迁移(迁移完成后恢复)
五、故障转移
5.1 故障检测
节点1(主节点)宕机:
↓
从节点1-1检测到主节点下线
↓
发起选举:向其他主节点请求投票
↓
获得多数投票 → 提升为主节点
↓
接管原主节点的槽位
5.2 选举规则
投票规则:
- 每个主节点有1票
- 从节点offset越大(数据越新),越容易获得投票
- 获得多数票(N/2+1)的从节点成为新主节点
5.3 手动故障转移
# 在从节点执行
127.0.0.1:7001> CLUSTER FAILOVER
# 强制故障转移(不等待数据同步)
127.0.0.1:7001> CLUSTER FAILOVER FORCE
六、集群扩容与缩容
6.1 扩容
添加主节点:
# 1. 添加节点
redis-cli --cluster add-node 127.0.0.1:7003 127.0.0.1:7000
# 2. 分配槽位
redis-cli --cluster reshard 127.0.0.1:7000
# 从现有节点迁移部分槽位到新节点
# 3. 添加从节点
redis-cli --cluster add-node 127.0.0.1:7004 127.0.0.1:7003 --cluster-slave
6.2 缩容
删除节点:
# 1. 迁移槽位到其他节点
redis-cli --cluster reshard 127.0.0.1:7000
# 将节点3的槽位全部迁移走
# 2. 删除从节点
redis-cli --cluster del-node 127.0.0.1:7004 <node-id>
# 3. 删除主节点
redis-cli --cluster del-node 127.0.0.1:7003 <node-id>
七、最佳实践
7.1 节点规划
✅ 推荐:
- 最少6个节点(3主3从)
- 奇数个主节点(3、5、7)
- 分布式部署(不同机器/机房)
7.2 槽位分配
推荐:
- 3个主节点:每个5461-5462个槽位
- 5个主节点:每个约3277个槽位
避免:
- 槽位分配不均(导致热点)
7.3 监控指标
# 集群状态
redis-cli --cluster check 127.0.0.1:7000
# 关键指标:
# - cluster_state: ok/fail
# - cluster_slots_assigned: 16384(全部分配)
# - cluster_slots_ok: 16384(全部正常)
# - cluster_known_nodes: 节点数量
7.4 客户端配置
Jedis示例:
import redis.clients.jedis.JedisCluster;
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("127.0.0.1", 7000));
nodes.add(new HostAndPort("127.0.0.1", 7001));
nodes.add(new HostAndPort("127.0.0.1", 7002));
JedisCluster cluster = new JedisCluster(nodes);
// 使用
cluster.set("key", "value");
String value = cluster.get("key");
// 批量操作(同一个哈希标签)
cluster.mset("{user:1001}:name", "Alice", "{user:1001}:age", "25");
八、Cluster局限性
8.1 不支持的命令
# ❌ 多key操作(key在不同节点)
MGET key1 key2 key3 # 可能在不同节点
# ✅ 使用哈希标签
MGET {user}:key1 {user}:key2 # 确保在同一节点
8.2 事务限制
# ❌ 多key事务(key在不同节点)
MULTI
SET key1 value1 # 可能在节点1
SET key2 value2 # 可能在节点2
EXEC
# ✅ 使用哈希标签或Lua脚本
8.3 性能开销
Gossip协议:
- 每秒发送PING/PONG消息
- 元数据传播
- 网络带宽占用(节点越多越明显)
建议:
- 节点数 < 1000
- 使用万兆网络
九、总结
核心要点
Cluster分片
- 16384个槽位
- CRC16(key) % 16384
- 哈希标签确保多key在同一节点
节点通信
- Gossip协议
- 去中心化
- 最终一致性
客户端重定向
- MOVED:永久重定向
- ASK:临时重定向
故障转移
- 自动检测故障
- 从节点自动提升
- 选举机制
扩容缩容
- 动态添加/删除节点
- 槽位在线迁移
最佳实践
- 最少6节点(3主3从)
- 奇数个主节点
- 使用哈希标签
第二阶段完成总结
🎉 恭喜!架构原理篇(10篇文章)全部完成!
已掌握的核心知识:
- Redis对象系统与内存模型
- SDS、跳表、字典、intset、ziplist底层数据结构
- 单线程模型与IO多路复用
- 主从复制、哨兵、Cluster高可用架构
下一阶段预告: 第三阶段《进阶特性篇》将深入:
- Lua脚本编程
- Bitmap、HyperLogLog、GEO、Stream高级数据类型
- 分布式锁、布隆过滤器
- 延迟队列、限流算法
期待继续学习!