一、Redis 集群模式全景
1. 集群模式对比
text
Redis 高可用方案演进:
├── 主从复制(Replication)
├── 哨兵模式(Sentinel)
└── 集群模式(Cluster)⭐ 主流方案
集群模式特点:
- 数据分片(Sharding):16384个哈希槽
- 高可用:主从自动故障转移
- 去中心化:Gossip协议通信
- 线性扩展:支持水平扩容
二、核心架构原理
1. 数据分片:哈希槽(Hash Slot)机制
bash
# 哈希槽范围:0-16383(共16384个槽)
# 键值对分配到槽的算法:
CRC16(key) % 16384
# 查看键所在槽
redis-cli -c CLUSTER KEYSLOT "user:1001"
# 输出:5478(表示该键在5478号槽)
2. 集群节点角色
text
集群节点类型:
├── 主节点(Master)
│ ├── 负责处理槽(0-16384的子集)
│ ├── 响应读写请求
│ └── 参与故障检测与选举
│
└── 从节点(Slave)
├── 复制主节点数据
├── 在主节点故障时升级为主节点
└── 提供读请求(可配置)
每个主节点应有1-N个从节点
最小集群配置:3主3从
3. 集群元数据管理
java
// 每个节点维护的集群状态
public class ClusterNode {
/*
节点信息(cluster nodes 命令输出):
<node-id> <ip:port@cport> <flags> <master-id>
<ping-sent> <pong-recv> <config-epoch>
<link-state> <slot1> <slot2> ... <slotN>
关键字段:
- node-id: 160bit唯一标识(与IP:PORT绑定)
- flags: master,slave,myself,fail?,handshake...
- slots: 该节点负责的槽位范围
- config-epoch: 配置纪元(故障转移用)
*/
}
三、集群搭建与数据分布
1. 集群创建过程
bash
# 1. 准备6个节点(3主3从示例)
redis-server --port 7000 --cluster-enabled yes --cluster-config-file nodes-7000.conf
redis-server --port 7001 --cluster-enabled yes --cluster-config-file nodes-7001.conf
# ... 直到7005
# 2. 创建集群(Redis 5.0+)
redis-cli --cluster create \
127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 \
127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \
--cluster-replicas 1
# 3. 集群会自动分配槽位
# 主节点:7000(0-5460), 7001(5461-10922), 7002(10923-16383)
# 从节点:7003复制7000,7004复制7001,7005复制7002
2. 槽位分配算法
java
// 槽位分配的核心逻辑
public class SlotAllocation {
/*
初始分配流程:
1. 为每个主节点生成一个长度为16384的位图
2. 按顺序轮流分配槽位:
节点A: 0, 3, 6, 9...
节点B: 1, 4, 7, 10...
节点C: 2, 5, 8, 11...
3. 最终每个主节点大约分配 16384/N 个槽
扩容时重新分配:
1. 计算每个节点应该持有的槽数 = 16384/(新节点数)
2. 从现有节点迁移部分槽到新节点
3. 迁移过程中保证数据可用性
*/
}
3. 查看集群状态
bash
# 查看集群信息
redis-cli -p 7000 cluster info
# 输出:
# cluster_state:ok # 集群状态
# cluster_slots_assigned:16384 # 已分配槽数
# cluster_slots_ok:16384 # 正常槽数
# cluster_known_nodes:6 # 已知节点数
# cluster_size:3 # 主节点数
# 查看节点信息
redis-cli -p 7000 cluster nodes
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
四、客户端请求处理流程
1. 智能客户端 vs 代理模式
java
// 智能客户端实现原理
public class SmartRedisClient {
// 客户端维护的槽位映射表
private Map<Integer, RedisNode> slotCache = new HashMap<>();
public Object executeCommand(String key, Command command) {
// 1. 计算key所在的槽
int slot = CRC16.crc16(key.getBytes()) % 16384;
// 2. 从缓存获取槽对应的节点
RedisNode targetNode = slotCache.get(slot);
if (targetNode == null) {
// 3. 缓存未命中,通过集群获取映射
targetNode = refreshSlotMapping();
slotCache.put(slot, targetNode);
}
// 4. 发送命令到目标节点
return sendToNode(targetNode, command);
}
// 处理MOVED/ASK重定向
private Object handleRedirect(RedirectResponse response) {
if (response.getType() == RedirectType.MOVED) {
// MOVED:永久重定向,更新槽位映射
slotCache.put(response.getSlot(), response.getNewNode());
// 重新执行命令
return retryCommand(response);
} else if (response.getType() == RedirectType.ASK) {
// ASK:临时重定向,不更新映射
// 先发送ASKING命令,再重试
sendASKING(response.getNewNode());
return retryCommand(response);
}
}
}
2. MOVED vs ASK 重定向
java
// 两种重定向的区别
public class RedirectMechanism {
/*
MOVED 重定向(永久):
场景:槽已经迁移到其他节点
客户端响应:更新本地槽位映射缓存
示例:MOVED 3999 127.0.0.1:6381
ASK 重定向(临时):
场景:槽正在迁移中(一部分数据在新节点)
客户端响应:不更新缓存,只本次请求重定向
示例:ASK 3999 127.0.0.1:6381
关键区别:
1. 持久性:MOVED更新缓存,ASK不更新
2. 时机:槽完全迁移完 vs 迁移过程中
3. 后续请求:MOVED后直接走新节点,ASK后可能还要问老节点
*/
}
3. 跨槽操作限制
bash
# Redis集群不支持跨槽操作
# 以下操作会报错:CROSSSLOT Keys in request don't hash to same slot
# 错误示例:MSET key1 val1 key2 val2
# 如果key1和key2在不同槽,会失败
# 解决方案1:使用hash tag
MSET {user:1001}:name "张三" {user:1001}:age 25
# 大括号{}中的内容用于计算槽位,确保相同user在同一个槽
# 解决方案2:客户端分批处理
# 解决方案3:使用Lua脚本(但脚本内的key也必须在同一节点)
五、故障检测与转移
1. Gossip 协议通信
java
// 节点间通过Gossip协议交换信息
public class GossipProtocol {
/*
通信机制:
1. PING/PONG消息
- 每个节点每秒随机选择几个节点发送PING
- 收到PING的节点回复PONG
- 消息包含:自身状态 + 已知的其他节点状态
2. 故障检测(主观下线 + 客观下线)
- 主观下线:节点A在cluster-node-timeout内未收到节点B的PONG
- 客观下线:超过半数主节点认为某节点主观下线
3. 信息传播
- 通过PING/PONG传播集群状态变更
- 最终一致性:信息最终会传播到所有节点
*/
}
2. 故障转移流程
java
// 自动故障转移实现
public class FailoverProcess {
/*
故障转移触发条件:
1. 主节点被标记为客观下线(FAIL状态)
2. 该主节点有从节点
3. 从节点与主节点复制连接正常
选举流程:
1. 资格检查
- 从节点与主节点断开时间 < cluster-node-timeout * cluster-slave-validity-factor
- 从节点的复制偏移量较新
2. 发起选举
- 从节点增加配置纪元(configEpoch)
- 向其他主节点请求投票
3. 投票机制
- 每个主节点在每个配置纪元只能投一票
- 收到超过半数主节点投票的从节点胜出
4. 故障转移
- 胜出的从节点执行SLAVEOF NO ONE
- 接管原主节点的槽位
- 广播新的配置信息
*/
}
3. 配置纪元(Config Epoch)
bash
# 配置纪元:集群的"逻辑时钟"
# 用于解决脑裂和配置冲突
# 查看节点的配置纪元
redis-cli -p 7000 cluster nodes | grep myself
# 输出包含:... 127.0.0.1:7000 myself,master - 0 0 1 connected 0-5460
# 最后的"1"就是配置纪元
# 配置纪元的作用:
# 1. 故障转移时递增,确保新主节点有更高的epoch
# 2. 解决网络分区恢复后的主从冲突
# 3. 保证配置信息的有序传播
六、数据迁移与扩容
1. 槽迁移流程
bash
# 集群扩容:添加新节点
# 1. 添加空节点
redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000
# 2. 分配槽位(从现有节点迁移)
redis-cli --cluster reshard 127.0.0.1:7000
# 交互式提示:
# How many slots do you want to move? 4096 # 要迁移的槽数
# What is the receiving node ID? [新节点ID]
# Source node #1: all # 从所有现有节点平均迁移
# 或指定具体节点:[原节点ID]
# 3. 添加从节点
redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7000 --cluster-slave --cluster-master-id [新主节点ID]
2. 迁移过程数据一致性
java
// 迁移过程中的读写处理
public class SlotMigration {
/*
迁移步骤:
1. 设置迁移状态
CLUSTER SETSLOT <slot> IMPORTING <source-node-id>
CLUSTER SETSLOT <slot> MIGRATING <target-node-id>
2. 迁移数据
- 对源节点:MIGRATE命令批量迁移key
- 迁移过程中,源节点继续处理请求
3. 请求处理规则:
源节点收到请求:
- 如果key存在且未迁移:正常处理
- 如果key不存在(已迁移):返回ASK重定向
- 如果key存在但正在迁移:阻塞客户端,迁移完成后返回
目标节点收到请求:
- 只有先收到ASKING命令后,才会处理重定向请求
4. 完成迁移
- 迁移完成后,广播槽位所有权变更
- 所有节点更新槽位映射
*/
}
3. 重新分片脚本示例
bash
#!/bin/bash
# 自动化重新分片脚本
CLUSTER_HOST="127.0.0.1:7000"
NEW_NODE="127.0.0.1:7006"
SLOTS_PER_NODE=4096
# 获取集群节点信息
NODES=$(redis-cli -c -h ${CLUSTER_HOST%:*} -p ${CLUSTER_HOST#*:} cluster nodes | grep master | awk '{print $2}')
# 为每个现有节点迁移部分槽到新节点
for NODE in $NODES; do
HOST=${NODE%:*}
PORT=${NODE#*:}
echo "从节点 $HOST:$PORT 迁移 $SLOTS_PER_NODE 个槽到新节点"
# 执行迁移
redis-cli --cluster reshard $HOST:$PORT \
--cluster-from $(redis-cli -h $HOST -p $PORT cluster myid) \
--cluster-to $(redis-cli -h ${NEW_NODE%:*} -p ${NEW_NODE#*:} cluster myid) \
--cluster-slots $SLOTS_PER_NODE \
--cluster-yes
done
七、集群运维与监控
1. 关键监控指标
bash
# 集群健康检查
redis-cli --cluster check 127.0.0.1:7000
# 监控指标采集
redis-cli -p 7000 info cluster
# 关键指标:
# cluster_state:ok # 集群状态
# cluster_slots_assigned:16384 # 已分配槽数
# cluster_slots_ok:16384 # 正常槽数
# cluster_slots_pfail:0 # 可能失败槽数
# cluster_slots_fail:0 # 已失败槽数
# cluster_known_nodes:6 # 节点数
# cluster_size:3 # 主节点数
# 节点延迟监控
redis-cli -p 7000 cluster nodes | awk '{print $1,$6,$7}'
# 输出:节点ID ping发送时间 pong接收时间
2. 运维命令大全
bash
# 1. 集群管理
redis-cli --cluster help # 查看所有集群命令
# 2. 节点管理
redis-cli --cluster add-node <new_host:port> <existing_host:port> # 添加节点
redis-cli --cluster del-node <host:port> <node_id> # 删除节点
redis-cli --cluster call <host:port> command arg1 arg2 ... # 在所有节点执行命令
# 3. 槽位管理
redis-cli --cluster reshard <host:port> # 重新分片
redis-cli --cluster rebalance <host:port> --cluster-threshold 2 # 平衡槽位
redis-cli --cluster fix <host:port> # 修复槽位
# 4. 数据迁移
redis-cli --cluster import <host:port> --cluster-from <source_host:port> --cluster-copy
# 5. 故障处理
redis-cli --cluster failover <host:port> --cluster-master-id <node_id> # 手动故障转移
3. 常见故障处理
bash
# 场景1:节点宕机恢复
# 如果主节点宕机后恢复,会变成从节点
# 手动恢复为主节点:
redis-cli -p 7000 cluster failover --force
# 场景2:脑裂问题
# 网络分区导致集群分裂,手动修复:
redis-cli --cluster fix 127.0.0.1:7000
# 场景3:槽位丢失
# 某些槽没有节点负责:
redis-cli --cluster fix 127.0.0.1:7000 --cluster-search-multiple-owners
# 场景4:从节点无法同步
# 检查主从连接状态:
redis-cli -p 7003 info replication
# 如果断开,手动重新同步:
redis-cli -p 7003 cluster replicate <master-node-id>
4. 性能优化配置
bash
# redis.conf 集群优化配置
# 网络相关
cluster-node-timeout 15000 # 节点超时时间(毫秒)
cluster-slave-validity-factor 10 # 从节点有效因子
cluster-require-full-coverage yes # 是否需要所有槽都覆盖
# 迁移优化
cluster-migration-barrier 1 # 主节点最少从节点数
cluster-replica-validity-factor 10 # 从节点复制有效性
# 性能优化
repl-disable-tcp-nodelay no # 主从复制启用Nagle算法
client-output-buffer-limit slave 256mb 64mb 60 # 从节点缓冲区
client-output-buffer-limit pubsub 32mb 8mb 60 # 发布订阅缓冲区
八、集群限制与解决方案
1. Redis 集群的限制
java
public class ClusterLimitations {
/*
1. 不支持跨节点事务
- MULTI/EXEC中的key必须在同一节点
- 解决方案:使用hash tag确保key在同一槽
2. 不支持跨节点Lua脚本
- 脚本中所有key必须在同一节点
- 解决方案:使用EVALSHA + hash tag
3. 键批量操作限制
- MGET/MSET等需要key在同一节点
- 解决方案:客户端分批或使用hash tag
4. 数据库数量限制
- 集群只支持db0
- 解决方案:用key前缀区分业务
5. Pub/Sub限制
- 发布订阅消息不会跨节点广播
- 解决方案:客户端连接到所有节点,或使用Redis Stream
*/
}
2. 生产环境最佳实践
yaml
# 集群部署规范
集群规模:
最小配置: 3主3从
推荐配置: 6主6从(生产环境)
最大规模: 1000节点(理论限制)
硬件配置:
内存: 主节点≤30GB(避免RDB/AOF过慢)
网络: 万兆网卡(迁移数据需要)
CPU: 至少4核(加密、压缩需要)
监控告警:
必须监控:
- 集群状态: cluster_state
- 槽位覆盖: cluster_slots_ok
- 节点数: cluster_known_nodes
- 内存使用率
- 网络延迟
告警阈值:
- 集群状态 != ok
- 槽位覆盖 < 16384
- 节点失联 > 1分钟
- 内存使用 > 80%
3. 与代理模式(Codis/Twemproxy)对比
markdown
| 特性 | Redis Cluster | Codis/Twemproxy |
|------|--------------|-----------------|
| 架构 | 去中心化,智能客户端 | 中心化代理 |
| 数据分片 | 客户端计算 | 代理计算 |
| 扩容 | 在线迁移,自动平衡 | 需要手动迁移,重启 |
| 运维复杂度 | 较高(需要理解集群协议) | 较低(简单代理) |
| 客户端支持 | 需要集群感知客户端 | 任何Redis客户端 |
| 故障转移 | 自动(秒级) | 依赖ZooKeeper/Etcd |
| 跨槽操作 | 不支持 | 支持(代理层处理) |
选择建议:
- 新项目:推荐Redis Cluster(官方维护,功能完整)
- 旧系统迁移:Codis可能更平滑
- 需要跨槽操作:考虑代理模式
九、客户端实现示例
1. Java客户端(Lettuce)
java
// Lettuce 集群客户端配置
@Configuration
public class RedisClusterConfig {
@Value("${spring.redis.cluster.nodes}")
private String clusterNodes;
@Value("${spring.redis.timeout}")
private Duration timeout;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisClusterConfiguration config = new RedisClusterConfiguration();
// 解析节点配置:host1:port1,host2:port2
String[] nodes = clusterNodes.split(",");
for (String node : nodes) {
String[] hostPort = node.split(":");
config.addClusterNode(new RedisNode(hostPort[0],
Integer.parseInt(hostPort[1])));
}
// 连接池配置
GenericObjectPoolConfig<Object> poolConfig =
new GenericObjectPoolConfig<>();
poolConfig.setMaxTotal(8);
poolConfig.setMaxIdle(8);
poolConfig.setMinIdle(0);
// 客户端配置
LettuceClientConfiguration clientConfig =
LettucePoolingClientConfiguration.builder()
.commandTimeout(timeout)
.poolConfig(poolConfig)
.build();
return new LettuceConnectionFactory(config, clientConfig);
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory());
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
2. 连接池优化配置
yaml
# application.yml 集群配置
spring:
redis:
cluster:
nodes: 192.168.1.101:7000,192.168.1.102:7000,192.168.1.103:7000
max-redirects: 3 # 最大重定向次数
lettuce:
pool:
max-active: 8 # 连接池最大连接数
max-idle: 8 # 最大空闲连接
min-idle: 0 # 最小空闲连接
max-wait: -1ms # 获取连接最大等待时间
cluster:
refresh:
adaptive: true # 自适应刷新拓扑
period: 2000ms # 刷新周期
timeout: 2000ms # 连接超时
3. 客户端最佳实践
java
// 生产环境客户端使用建议
public class RedisClusterBestPractice {
// 1. 使用连接池,避免频繁创建连接
@Resource
private RedisTemplate<String, Object> redisTemplate;
// 2. 批量操作使用pipeline(同一节点)
public void batchOperations() {
List<String> keys = Arrays.asList("{user:1001}:name",
"{user:1001}:age");
// 使用hash tag确保在同一节点
redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
for (String key : keys) {
connection.get(key.getBytes());
}
return null;
});
}
// 3. 处理重定向异常
public Object safeGet(String key) {
try {
return redisTemplate.opsForValue().get(key);
} catch (RedisClusterException e) {
if (e.getMessage().contains("MOVED") ||
e.getMessage().contains("ASK")) {
// 客户端会自动处理重定向
// 记录日志用于监控
log.warn("Redis集群重定向: {}", e.getMessage());
// 重试一次
return redisTemplate.opsForValue().get(key);
}
throw e;
}
}
// 4. 监控连接状态
@Scheduled(fixedDelay = 30000)
public void checkClusterHealth() {
try {
String clusterInfo = redisTemplate
.getConnectionFactory()
.getConnection()
.clusterInfo();
log.info("集群状态: {}", clusterInfo);
} catch (Exception e) {
log.error("集群健康检查失败", e);
// 触发告警
}
}
}
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
十、总结
Redis Cluster 核心要点:
markdown
1. **数据分片**:16384个哈希槽,CRC16(key) % 16384
2. **高可用**:主从复制 + 自动故障转移
3. **去中心化**:Gossip协议通信,无单点故障
4. **线性扩展**:支持在线扩容缩容
适用场景:
✅ 大数据量(单机内存不足)
✅ 高并发读写
✅ 需要高可用
✅ 预算有限(相比Codis无代理层开销)
不适用场景:
❌ 需要跨节点事务
❌ 大量使用Lua脚本(跨节点)
❌ 对运维复杂度敏感的小团队
生产环境部署清单:
bash
# 部署前检查清单
□ 1. 节点数:至少3主3从
□ 2. 内存:每个节点≤30GB
□ 3. 网络:节点间延迟<5ms
□ 4. 配置:cluster-node-timeout合理设置
□ 5. 监控:集群状态、槽位覆盖、节点健康
□ 6. 备份:定期RDB/AOF备份
□ 7. 客户端:使用支持集群的智能客户端
□ 8. 压测:模拟故障转移,验证高可用
最终建议:Redis Cluster 是目前最成熟、最推荐的Redis分布式方案。理解其核心原理(槽分片、Gossip协议、故障转移)对于生产环境运维至关重要。对于新项目,建议直接使用Redis Cluster;对于已有系统迁移,需要评估业务是否使用了集群不支持的特性。