一、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;对于已有系统迁移,需要评估业务是否使用了集群不支持的特性。