难度:⭐⭐⭐⭐⭐ | 适合人群:想掌握Redis大规模部署方案的开发者
💥 开场:一次"爆仓"危机
时间: 双12大促前一天
地点: 公司作战室
事件: 容量评估
技术总监: "明天预计流量是平时的50倍,Redis能扛住吗?"
我: "应该没问题,我们用的主从 + 哨兵架构。"
哈吉米: "等等,你们现在数据量多大?"
我: "大概80GB。"
南北绿豆: "Redis单机内存多大?"
我: "100GB,还有20GB余量。" 😊
阿西噶阿西: "那明天流量暴增,数据量也会暴增啊!"
我: "这..." 😰
紧急计算:
当前数据量:80GB
预计流量增长:50倍
预计数据量:80GB × 5 = 400GB
单机内存:100GB
↓
内存不够!💥
技术总监: "怎么办?"
我: "加内存?买个1TB内存的服务器?" 🤔
哈吉米: "来不及了!而且单机内存太大,持久化都会很慢。"
南北绿豆: "这种情况必须用Redis Cluster集群,数据分片存储!"
阿西噶阿西: "对,多台机器分担数据,每台只存一部分,我给你们讲讲..."
🎯 第一问:为什么需要Cluster?
主从架构的局限性
哈吉米画图:
主从 + 哨兵架构:
Master(100GB数据)
/ | \
Slave1 Slave2 Slave3
(100GB)(100GB)(100GB)
问题:
1. 每个节点都存储全量数据
2. 容量受限于单机内存
3. 写操作都在Master,压力大
4. 数据量大时,持久化慢
Cluster集群架构
Redis Cluster(数据分片):
Node1(33GB) Node2(33GB) Node3(34GB)
↓ ↓ ↓
Slot 0-5460 Slot 5461-10922 Slot 10923-16383
总数据:100GB
每个节点:约33GB
优势:
✅ 容量可扩展(加节点)
✅ 写操作分散到多个节点
✅ 高可用(节点挂了,数据不丢)
Cluster vs 主从对比
| 维度 | 主从+哨兵 | Cluster集群 |
|---|---|---|
| 数据存储 | 每个节点全量数据 | 数据分片存储 |
| 容量 | 受限于单机内存 | 可水平扩展 |
| 写性能 | Master单点 | 分散到多个节点 |
| 高可用 | 哨兵故障转移 | 自动故障转移 |
| 适用场景 | 数据量小(<100GB) | 数据量大(>100GB) |
| 复杂度 | 简单 | 较复杂 |
🎲 第二问:Slot(槽)分片原理
什么是Slot?
南北绿豆: "Redis Cluster把数据分成16384个槽(Slot)。"
16384个槽(0-16383)
↓
分配给不同节点
↓
每个key通过哈希算法映射到某个槽
↓
存储到对应节点
槽分配
3个节点的分配:
Node1:Slot 0-5460 (5461个槽)
Node2:Slot 5461-10922 (5462个槽)
Node3:Slot 10923-16383 (5461个槽)
总共:16384个槽
6个节点的分配:
Node1:Slot 0-2729
Node2:Slot 2730-5460
Node3:Slot 5461-8191
Node4:Slot 8192-10922
Node5:Slot 10923-13652
Node6:Slot 13653-16383
数据路由算法
哈吉米: "key如何映射到槽?"
// 计算槽位
int slot = CRC16(key) % 16384;
// 例子:
CRC16("user:1") = 52143
52143 % 16384 = 2991
↓
Slot 2991 → 在Node2(5461-10922)
↓
数据存储到Node2
Hash Tag(哈希标签):
问题: 如果想让某些key存在同一个节点怎么办?
// 使用Hash Tag:{}中的内容用于计算槽
String key1 = "user:{123}:profile";
String key2 = "user:{123}:orders";
String key3 = "user:{123}:cart";
// 计算槽时,只用{123}计算
CRC16("123") % 16384 = 5555
↓
三个key都在Slot 5555,存在同一个节点
↓
可以使用MGET等批量操作
🔄 第三问:数据重定向
MOVED重定向
客户端请求流程:
sequenceDiagram
participant Client
participant Node1
participant Node2
Client->>Node1: GET user:1
Note over Node1: 计算槽位:2991<br/>不在Node1(0-5460)
Node1-->>Client: MOVED 2991 192.168.1.102:6379
Note over Client: 收到重定向
Client->>Node2: GET user:1
Note over Node2: 槽2991在Node2
Node2-->>Client: "张三"
ASK重定向
槽迁移过程中:
sequenceDiagram
participant Client
participant Node1
participant Node2
Note over Node1,Node2: 槽2991正在从Node1迁移到Node2
Client->>Node1: GET user:1
Note over Node1: 槽2991正在迁移中<br/>key可能在Node2
Node1-->>Client: ASK 2991 192.168.1.102:6379
Client->>Node2: ASKING
Client->>Node2: GET user:1
Node2-->>Client: "张三"
MOVED vs ASK
阿西噶阿西: "两种重定向的区别:"
| 类型 | 含义 | 客户端行为 | 场景 |
|---|---|---|---|
| MOVED | 槽已经迁移完成 | 更新本地槽映射 | 正常请求 |
| ASK | 槽正在迁移中 | 临时重定向,不更新映射 | 槽迁移中 |
⚙️ 第四问:集群配置与搭建
最小集群(3主3从)
配置文件(每个节点一个):
# redis-7000.conf(Master1)
port 7000
cluster-enabled yes # 启用集群
cluster-config-file nodes-7000.conf # 集群配置文件
cluster-node-timeout 5000 # 节点超时时间
appendonly yes
daemonize yes
pidfile /var/run/redis-7000.pid
logfile /var/log/redis/redis-7000.log
dir /var/lib/redis/7000
其他5个节点类似(改端口和路径):
redis-7000.conf(Master1)
redis-7001.conf(Master2)
redis-7002.conf(Master3)
redis-7003.conf(Slave1 - 对应Master1)
redis-7004.conf(Slave2 - 对应Master2)
redis-7005.conf(Slave3 - 对应Master3)
启动节点
# 启动6个Redis实例
redis-server /etc/redis/redis-7000.conf
redis-server /etc/redis/redis-7001.conf
redis-server /etc/redis/redis-7002.conf
redis-server /etc/redis/redis-7003.conf
redis-server /etc/redis/redis-7004.conf
redis-server /etc/redis/redis-7005.conf
# 检查是否启动
ps aux | grep redis
创建集群
Redis 5.0+使用redis-cli:
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
参数说明:
--cluster-replicas 1:每个Master有1个Slave
创建过程:
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 192.168.1.100:7003 to 192.168.1.100:7000
Adding replica 192.168.1.100:7004 to 192.168.1.100:7001
Adding replica 192.168.1.100:7005 to 192.168.1.100:7002
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join...
>>> Performing Cluster Check
[OK] All nodes agree about slots configuration.
[OK] All 16384 slots covered.
查看集群状态
# 连接任意节点
redis-cli -c -p 7000
# 查看集群信息
redis> CLUSTER INFO
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_known_nodes:6
cluster_size:3
# 查看节点信息
redis> CLUSTER NODES
a1b2c3d4... 192.168.1.100:7000@17000 myself,master - 0 1705900000 1 connected 0-5460
e5f6g7h8... 192.168.1.100:7001@17001 master - 0 1705900001 2 connected 5461-10922
i9j0k1l2... 192.168.1.100:7002@17002 master - 0 1705900001 3 connected 10923-16383
m3n4o5p6... 192.168.1.100:7003@17003 slave a1b2c3d4... 0 1705900002 1 connected
q7r8s9t0... 192.168.1.100:7004@17004 slave e5f6g7h8... 0 1705900002 2 connected
u1v2w3x4... 192.168.1.100:7005@17005 slave i9j0k1l2... 0 1705900003 3 connected
📊 第五问:集群扩容与缩容
扩容:新增节点
场景: 数据量又增长了,3个节点不够,要加到6个
步骤1:启动新节点
# 启动2个新节点(1主1从)
redis-server /etc/redis/redis-7006.conf
redis-server /etc/redis/redis-7007.conf
步骤2:加入集群
# 添加Master节点
redis-cli --cluster add-node 192.168.1.100:7006 192.168.1.100:7000
# 添加Slave节点
redis-cli --cluster add-node 192.168.1.100:7007 192.168.1.100:7000 \
--cluster-slave \
--cluster-master-id <7006节点的ID>
步骤3:重新分配槽
# 重新分片
redis-cli --cluster reshard 192.168.1.100:7000
# 交互式操作:
How many slots do you want to move? 4096 # 移动4096个槽给新节点
What is the receiving node ID? <7006节点ID>
Source node: all # 从所有节点平均分配
分片过程:
sequenceDiagram
participant Node1
participant Node2
participant Node3
participant Node4(new)
Note over Node1,Node3: 原来的槽分配<br/>Node1: 0-5460<br/>Node2: 5461-10922<br/>Node3: 10923-16383
Node1->>Node4(new): 迁移槽 0-1364
Node2->>Node4(new): 迁移槽 5461-6825
Node3->>Node4(new): 迁移槽 10923-12287
Note over Node1,Node4(new): 新的槽分配<br/>Node1: 1365-5460<br/>Node2: 6826-10922<br/>Node3: 12288-16383<br/>Node4: 0-1364, 5461-6825, 10923-12287
缩容:删除节点
步骤1:迁移槽
# 把要删除节点的槽迁移到其他节点
redis-cli --cluster reshard 192.168.1.100:7000 \
--cluster-from <要删除节点的ID> \
--cluster-to <目标节点ID> \
--cluster-slots <槽数量>
步骤2:删除节点
# 先删除Slave
redis-cli --cluster del-node 192.168.1.100:7007 <7007节点ID>
# 再删除Master(必须已经没有槽)
redis-cli --cluster del-node 192.168.1.100:7006 <7006节点ID>
💥 第六问:故障转移
自动故障转移
当Master宕机时:
sequenceDiagram
participant Slave1
participant Slave2
participant Master
participant Node2
participant Node3
Note over Master: Master宕机!💥
Slave1->>Master: PING
Note over Slave1: 无响应
Slave1->>Slave1: 标记Master为PFAIL<br/>(主观下线)
Slave1->>Node2: 询问:Master状态?
Node2-->>Slave1: PFAIL
Slave1->>Node3: 询问:Master状态?
Node3-->>Slave1: PFAIL
Note over Slave1: 超过半数节点认为PFAIL<br/>标记为FAIL(客观下线)
Slave1->>Slave1: 发起选举
Note over Slave1: 我要升级为Master
Node2->>Slave1: 投票
Node3->>Slave1: 投票
Note over Slave1: 获得超过半数投票<br/>升级为Master
Slave1->>Slave1: SLAVEOF NO ONE
Note over Slave1: 现在是Master了
Slave1->>Node2: 广播:我是新Master
Slave1->>Node3: 广播:我是新Master
Slave2->>Slave1: 复制新Master
故障转移条件
触发条件:
1. Master被标记为FAIL
2. Master负责的槽至少有一个Slave
3. Slave数据最新(复制偏移量最大)
选举规则:
1. 复制偏移量最大的Slave优先
2. 获得超过半数节点投票
3. 升级为Master
💻 第七问:Java客户端使用
Jedis连接集群
@Configuration
public class RedisClusterConfig {
@Bean
public JedisCluster jedisCluster() {
// 集群节点
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("192.168.1.100", 7000));
nodes.add(new HostAndPort("192.168.1.100", 7001));
nodes.add(new HostAndPort("192.168.1.100", 7002));
nodes.add(new HostAndPort("192.168.1.100", 7003));
nodes.add(new HostAndPort("192.168.1.100", 7004));
nodes.add(new HostAndPort("192.168.1.100", 7005));
// 连接池配置
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(100);
poolConfig.setMaxIdle(20);
poolConfig.setMinIdle(10);
// 创建集群连接
return new JedisCluster(
nodes, // 节点列表
2000, // 连接超时
2000, // 读取超时
5, // 重试次数
"password123", // 密码
poolConfig // 连接池配置
);
}
}
使用:
@Service
public class UserService {
@Autowired
private JedisCluster jedisCluster;
public void setUser(String userId, String userInfo) {
// 自动路由到正确的节点
jedisCluster.set("user:" + userId, userInfo);
}
public String getUser(String userId) {
return jedisCluster.get("user:" + userId);
}
/**
* 批量操作(必须用Hash Tag)
*/
public void batchSet(String userId, Map<String, String> data) {
// 使用Hash Tag保证在同一个槽
for (Map.Entry<String, String> entry : data.entrySet()) {
String key = "user:{" + userId + "}:" + entry.getKey();
jedisCluster.set(key, entry.getValue());
}
}
}
Redisson连接集群
@Configuration
public class RedissonClusterConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
// 集群模式配置
config.useClusterServers()
.addNodeAddress(
"redis://192.168.1.100:7000",
"redis://192.168.1.100:7001",
"redis://192.168.1.100:7002",
"redis://192.168.1.100:7003",
"redis://192.168.1.100:7004",
"redis://192.168.1.100:7005"
)
.setPassword("password123")
.setMasterConnectionMinimumIdleSize(10)
.setMasterConnectionPoolSize(64)
.setSlaveConnectionMinimumIdleSize(10)
.setSlaveConnectionPoolSize(64)
.setReadMode(ReadMode.SLAVE) // 读从Slave
.setSubscriptionMode(SubscriptionMode.MASTER);
return Redisson.create(config);
}
}
🎨 第八问:集群常用命令
集群管理命令
# 查看集群信息
CLUSTER INFO
# 查看集群节点
CLUSTER NODES
# 查看槽分配
CLUSTER SLOTS
# 查看某个key在哪个槽
CLUSTER KEYSLOT user:1
# 输出:2991
# 查看某个槽有多少key
CLUSTER COUNTKEYSINSLOT 2991
# 获取某个槽的key列表
CLUSTER GETKEYSINSLOT 2991 10 # 获取10个key
槽管理命令
# 将槽分配给节点
CLUSTER ADDSLOTS 0 1 2 3 4 5
# 移除节点的槽
CLUSTER DELSLOTS 0 1 2 3 4 5
# 设置槽的迁移状态
CLUSTER SETSLOT 2991 MIGRATING <目标节点ID>
CLUSTER SETSLOT 2991 IMPORTING <源节点ID>
CLUSTER SETSLOT 2991 NODE <节点ID>
💡 最佳实践
1. 节点数量规划
推荐:
至少6个节点(3主3从)
生产环境:6-12个节点
原因:
- 至少3个Master(分片)
- 每个Master至少1个Slave(高可用)
- 奇数个Master(便于选举)
不推荐:
- 少于6个节点:可用性不够
- 超过20个节点:管理复杂
2. 硬件配置
推荐配置(每个节点):
CPU:8核
内存:32GB-64GB
磁盘:SSD 500GB
网络:万兆网卡
数据分布:
每个节点存储总数据的 1/N(N=Master数量)
例如:
总数据300GB,3个Master
每个Master约100GB
建议单机内存:128GB
3. 槽分配优化
# 手动优化槽分配(让分布更均匀)
redis-cli --cluster rebalance 192.168.1.100:7000 \
--cluster-use-empty-masters # 包括空Master
4. 监控指标
# 监控这些指标
# 1. 集群状态
CLUSTER INFO
cluster_state # 必须是ok
# 2. 节点状态
CLUSTER NODES
# 检查是否有fail的节点
# 3. 槽覆盖
cluster_slots_assigned # 必须是16384
cluster_slots_ok # 必须是16384
# 4. 内存使用
INFO memory
used_memory_human
# 5. 网络延迟
CLUSTER NODES
# 查看节点间延迟
💡 知识点总结
Redis Cluster核心要点
✅ Cluster架构
- 数据分片存储(16384个槽)
- 无中心架构(节点对等)
- 自动故障转移
✅ 槽分片
- CRC16(key) % 16384计算槽位
- Hash Tag控制key分布
- 槽均匀分配到Master
✅ 数据路由
- MOVED重定向(槽已迁移)
- ASK重定向(槽迁移中)
- 客户端智能路由
✅ 扩容缩容
- 添加节点:add-node
- 分配槽:reshard
- 删除节点:del-node
✅ 故障转移
- 主观下线(PFAIL)
- 客观下线(FAIL)
- Slave自动升级为Master
✅ 最佳实践
- 至少6个节点(3主3从)
- 每个Master有Slave
- 监控集群状态
记忆口诀
Cluster集群数据分,
一万六千槽来存。
哈希算法定位准,
三个Master是基础。
每个Master带Slave,
故障转移自动搞。
扩容加节点再分片,
缩容迁移槽再删。
MOVED槽已迁移完,
ASK迁移进行中。
生产至少六节点,
高可用来又能扩。
🤔 常见面试题
Q1: Redis Cluster如何分片?
A:
分片方式:
1. 16384个槽(0-16383)
2. CRC16(key) % 16384 计算槽位
3. 每个Master负责一部分槽
4. 数据根据槽位存储到对应Master
例如:
3个Master:
Master1: 0-5460
Master2: 5461-10922
Master3: 10923-16383
key "user:1" → CRC16 → 槽2991 → 存到Master1
Q2: Cluster如何实现高可用?
A:
高可用机制:
1. 每个Master配Slave
- Master挂了,Slave自动升级
2. 故障检测
- 节点间互相PING
- 超过半数认为下线才算下线
3. 自动故障转移
- Slave发起选举
- 获得半数以上投票
- 升级为Master
4. 客户端自动切换
- 连接哨兵或集群
- 自动发现新Master
Q3: Cluster vs 主从+哨兵?
A:
选择Cluster的场景:
✅ 数据量大(>100GB)
✅ 需要横向扩展
✅ 写操作压力大
选择主从+哨兵的场景:
✅ 数据量小(<100GB)
✅ 架构简单
✅ 运维成本低
对比:
容量 扩展性 复杂度 适用场景
Cluster 大 可扩展 复杂 大数据量
主从 受限 不可扩展 简单 中小数据量
Q4: Cluster有什么限制?
A:
限制:
1. 批量操作受限
- MGET、MSET只能操作同一槽的key
- 需要用Hash Tag
2. 事务受限
- 只能对同一槽的key进行事务
3. 数据库选择受限
- 只能使用DB 0
4. 复制结构单一
- 只支持一层复制(Master-Slave)
- 不支持Slave的Slave
💬 写在最后
从单机到主从,再到集群,我们深入学习了Redis的架构演进:
- 🎲 理解了Slot槽分片原理
- 🔄 掌握了数据路由和重定向
- ⚙️ 学会了集群的搭建和管理
- 📊 完成了扩容缩容操作
这篇文章,希望能让你掌握Redis大规模部署方案!
如果这篇文章对你有帮助,请:
- 👍 点赞支持
- ⭐ 收藏备用
- 🔄 转发分享
- 💬 评论交流