📚 文章概述
Redis Cluster是Redis官方提供的分布式解决方案,它通过数据分片、节点通信、故障转移等机制实现了高可用、高性能的分布式Redis集群。本文将深入解析Redis Cluster的架构设计、哈希槽分配机制、Gossip通信协议、数据路由原理,以及集群的扩容缩容操作,帮助读者全面理解Redis Cluster的工作原理和最佳实践。
一、理论部分
1.1 Redis Cluster概述
1.1.1 为什么需要Redis Cluster?
单机Redis的局限性:
- 容量限制:单机内存容量有限
- 性能瓶颈:单机处理能力有限
- 单点故障:主从复制和哨兵模式仍存在单点问题
- 扩展困难:无法水平扩展
Redis Cluster的优势:
- 水平扩展:可以动态添加节点,扩展容量和性能
- 高可用:自动故障转移,无单点故障
- 数据分片:数据自动分布到多个节点
- 透明路由:客户端自动路由到正确的节点
1.1.2 Redis Cluster架构
Gossip协议 Gossip协议 Gossip协议 Gossip协议 Gossip协议 Gossip协议 Gossip协议 Gossip协议 Gossip协议 客户端 Cluster节点1 Cluster节点2 Cluster节点3 Cluster节点4 Cluster节点5 Cluster节点6
架构特点:
- 无中心化:所有节点地位平等,无中心节点
- 数据分片:使用哈希槽(Hash Slot)分片
- 节点通信:使用Gossip协议进行节点间通信
- 故障转移:自动检测和转移故障节点
1.2 哈希槽(Hash Slot)机制
1.2.1 哈希槽概念
Redis Cluster将整个键空间分为16384个槽(slot),每个键通过CRC16算法计算后对16384取模,得到对应的槽号。
哈希槽分配:
键空间 CRC16算法 对16384取模 哈希槽 0-16383 分配到节点
计算公式:
slot = CRC16(key) % 16384
为什么是16384?
- 2^14 = 16384,便于位运算
- 节点间心跳包可以携带完整的槽分配信息
- 平衡了分片粒度和通信开销
1.2.2 哈希槽分配
初始分配:
16384个哈希槽 节点1: 0-5460 节点2: 5461-10922 节点3: 10923-16383
3主3从架构的槽分配:
16384个哈希槽 主节点1: 0-5460 主节点2: 5461-10922 主节点3: 10923-16383 从节点1 从节点2 从节点3
槽分配规则:
- 每个主节点负责一部分哈希槽
- 所有16384个槽必须被分配
- 槽可以重新分配(扩容/缩容时)
1.2.3 键到槽的映射
单个键:
bash
# 计算键的槽号
CLUSTER KEYSLOT mykey
# 返回: 1620
键标签(Hash Tags):
为了将相关的键分配到同一个槽,可以使用键标签:
bash
# 格式:{tag}key
user:{1000}:name # 槽号基于 {1000} 计算
user:{1000}:email # 槽号基于 {1000} 计算
# 这两个键会被分配到同一个槽
键标签示例:
1.3 节点通信协议(Gossip)
1.3.1 Gossip协议原理
Gossip协议是一种去中心化的通信协议,节点之间通过随机选择其他节点进行信息交换。
Gossip协议特点:
- 去中心化:无中心节点,所有节点地位平等
- 随机通信:随机选择节点进行信息交换
- 最终一致性:信息最终会传播到所有节点
- 容错性强:部分节点故障不影响整体通信
Gossip通信过程:
Node1 Node2 Node3 Node4 每100ms执行一次 PING (随机选择) PONG + 集群信息 PING (随机选择) PONG + 集群信息 PING (随机选择) PONG + 集群信息 信息逐渐传播到所有节点 Node1 Node2 Node3 Node4
1.3.2 节点消息类型
1. MEET消息
- 用于将新节点加入集群
- 格式:
CLUSTER MEET <ip> <port>
2. PING消息
- 用于检测节点是否在线
- 每100ms随机选择一个节点发送PING
- 包含发送节点的集群信息
3. PONG消息
- 响应PING或MEET消息
- 包含节点的集群信息
4. FAIL消息
- 节点检测到其他节点故障时广播
- 触发故障转移
5. PUBLISH消息
- 用于发布订阅功能
消息传播:
节点1检测到节点2故障 广播FAIL消息 节点3收到FAIL消息 节点4收到FAIL消息 节点5收到FAIL消息 更新集群状态
1.3.3 集群状态传播
集群状态信息包括:
- 节点列表和角色(主/从)
- 哈希槽分配信息
- 节点故障信息
- 配置纪元(config epoch)
状态传播过程:
节点1状态变更 通过Gossip传播 节点2更新状态 节点3更新状态 节点4更新状态 继续传播 最终所有节点状态一致
1.4 数据路由原理
1.4.1 客户端路由
路由流程:
有 无 是 否 客户端发送命令 本地缓存有槽信息? 计算键的槽号 连接任意节点 槽在当前节点? 执行命令 返回MOVED重定向 获取集群信息 更新本地缓存 客户端重定向到正确节点
1.4.2 MOVED重定向
当客户端访问错误的节点时,节点会返回MOVED错误:
bash
MOVED <slot> <ip>:<port>
客户端处理:
- 解析MOVED错误
- 更新本地槽缓存
- 重定向到正确的节点
- 重新执行命令
MOVED重定向示例:
Client Node1 Node2 GET mykey 计算槽号: 5000 不在本节点 MOVED 5000 192.168.1.102:6379 更新槽缓存 GET mykey value Client Node1 Node2
1.4.3 ASK重定向
ASK重定向发生在集群扩容/缩容过程中,数据正在迁移时:
bash
ASK <slot> <ip>:<port>
ASK vs MOVED:
- MOVED:槽已永久迁移,更新缓存
- ASK:槽正在迁移,临时重定向,不更新缓存
ASK重定向流程:
Client Node1 Node2 GET mykey 槽5000正在迁移 键可能在新节点 ASK 5000 192.168.1.102:6379 ASKING GET mykey value 不更新槽缓存 下次仍访问Node1 Client Node1 Node2
1.5 故障检测与转移
1.5.1 故障检测
检测机制:
Node1 Node2 Node3 PING PONG 标记为疑似故障(PFAIL) alt [Node2正常] [Node2无响应] loop [每1秒] 询问Node2状态 Node2也疑似故障 标记为确认故障(FAIL) 广播FAIL消息 Node1 Node2 Node3
故障判定:
- 节点A向节点B发送PING,超过
cluster-node-timeout时间无响应 - 节点A标记节点B为PFAIL(疑似故障)
- 节点A询问其他节点关于节点B的状态
- 如果大多数主节点都认为节点B故障,标记为FAIL(确认故障)
1.5.2 故障转移
故障转移流程:
是 否 主节点故障 从节点检测到故障 从节点延迟随机时间 从节点发起选举 获得大多数主节点投票? 从节点提升为主节点 等待下一轮选举 接管原主节点的槽 广播PONG消息 故障转移完成
选举机制:
- 从节点延迟随机时间(0-1000ms),避免同时发起选举
- 向所有主节点请求投票
- 主节点只能投票给一个从节点
- 获得大多数主节点投票的从节点成为新主节点
配置纪元(Config Epoch):
- 每个节点维护一个配置纪元
- 故障转移时,新主节点的配置纪元会递增
- 用于解决冲突,配置纪元大的节点优先
1.6 集群扩容
1.6.1 扩容流程
扩容步骤:
添加新节点 新节点加入集群 将新节点设置为从节点 迁移哈希槽到新节点 将新节点提升为主节点 扩容完成
详细流程:
-
添加新节点
bash# 在新节点启动Redis redis-server --cluster-enabled yes --cluster-config-file nodes.conf # 将新节点加入集群 redis-cli --cluster add-node new_node:6379 existing_node:6379 -
迁移哈希槽
bash# 迁移槽到新节点 redis-cli --cluster reshard existing_node:6379 # 输入要迁移的槽数量 # 输入目标节点ID # 输入源节点ID(all表示所有节点) -
验证扩容
bash# 检查集群状态 redis-cli --cluster check new_node:6379
1.6.2 槽迁移过程
迁移流程:
Client SourceNode TargetNode 开始迁移槽5000 MIGRATE key 接收数据 GET key ASK重定向 ASKING + GET key value value alt [键已迁移] [键未迁移] 迁移完成 确认迁移完成 更新槽分配 Client SourceNode TargetNode
迁移命令:
CLUSTER SETSLOT <slot> MIGRATING <node-id>:标记槽为迁移中CLUSTER SETSLOT <slot> IMPORTING <node-id>:标记槽为导入中MIGRATE <host> <port> <key> <db> <timeout>:迁移键
1.7 集群缩容
1.7.1 缩容流程
缩容步骤:
是 否 选择要移除的节点 是主节点? 迁移该节点的所有槽 直接移除 迁移完成 将节点从集群移除 缩容完成
详细流程:
-
迁移槽
bash# 将节点A的槽迁移到节点B redis-cli --cluster reshard node_a:6379 # 输入要迁移的槽数量 # 输入目标节点ID(节点B) # 输入源节点ID(节点A) -
移除节点
bash# 移除节点 redis-cli --cluster del-node node_a:6379 <node-id>
1.7.2 注意事项
- 主节点缩容:必须先迁移所有槽
- 从节点缩容:可以直接移除
- 数据安全:确保数据已迁移完成
- 集群状态:移除后检查集群状态
1.8 集群配置
1.8.1 基本配置
conf
# 启用集群模式
cluster-enabled yes
# 集群配置文件
cluster-config-file nodes.conf
# 节点超时时间(毫秒)
cluster-node-timeout 15000
# 集群迁移相关
cluster-migration-barrier 1
cluster-require-full-coverage yes
关键配置说明:
- cluster-enabled:必须设置为yes才能启用集群模式
- cluster-config-file:集群配置文件,自动生成
- cluster-node-timeout:节点超时时间,超过此时间认为节点故障
- cluster-migration-barrier:主节点至少保留的从节点数量
- cluster-require-full-coverage:是否要求所有槽都被覆盖
1.8.2 集群创建
方式1:使用redis-cli工具
bash
# 创建集群(3主3从)
redis-cli --cluster create \
192.168.1.100:6379 \
192.168.1.101:6379 \
192.168.1.102:6379 \
192.168.1.103:6379 \
192.168.1.104:6379 \
192.168.1.105:6379 \
--cluster-replicas 1
方式2:手动创建
bash
# 1. 启动所有节点(启用集群模式)
redis-server --cluster-enabled yes
# 2. 节点互相MEET
redis-cli -h node1 -p 6379 CLUSTER MEET node2 6379
redis-cli -h node1 -p 6379 CLUSTER MEET node3 6379
# ... 所有节点互相MEET
# 3. 分配槽
redis-cli -h node1 -p 6379 CLUSTER ADDSLOTS {0..5460}
redis-cli -h node2 -p 6379 CLUSTER ADDSLOTS {5461..10922}
redis-cli -h node3 -p 6379 CLUSTER ADDSLOTS {10923..16383}
# 4. 设置主从关系
redis-cli -h node4 -p 6379 CLUSTER REPLICATE <node1-id>
redis-cli -h node5 -p 6379 CLUSTER REPLICATE <node2-id>
redis-cli -h node6 -p 6379 CLUSTER REPLICATE <node3-id>
1.9 集群限制
1.9.1 功能限制
不支持的命令:
- 多键操作(除非使用键标签)
- 事务(MULTI/EXEC)
- Lua脚本(涉及多键)
- 数据库选择(SELECT命令)
原因:
- 这些操作需要访问多个节点
- 集群模式下键分布在不同节点
- 无法保证原子性
1.9.2 键标签使用
多键操作示例:
bash
# ❌ 错误:键在不同节点
MSET user:1000:name "Alice" user:2000:name "Bob"
# ✅ 正确:使用键标签
MSET user:{1000}:name "Alice" user:{1000}:email "alice@example.com"
# 两个键都在同一个槽
键标签规则:
- 格式:
{tag}key - 只有
{}中的部分用于计算槽号 - 可以强制相关键在同一槽
1.10 集群监控
1.10.1 集群状态检查
bash
# 检查集群状态
redis-cli --cluster check <node>:<port>
# 查看集群信息
redis-cli -h <node> -p <port> CLUSTER INFO
# 查看节点信息
redis-cli -h <node> -p <port> CLUSTER NODES
1.10.2 关键指标
集群健康指标:
cluster_state:集群状态(ok/fail)cluster_slots_assigned:已分配的槽数cluster_slots_ok:正常槽数cluster_known_nodes:已知节点数cluster_size:主节点数量
节点状态指标:
connected:连接的节点数cluster_epoch:配置纪元node_id:节点ID
二、实践指南
2.1 集群部署规划
2.1.1 节点数量规划
最小配置:
- 3个主节点(每个主节点至少1个从节点)
- 总共6个节点
推荐配置:
- 根据数据量和性能需求确定主节点数量
- 每个主节点配置1-2个从节点
- 主节点数量建议为奇数
节点规划示例:
集群规划 小型集群: 3主3从 中型集群: 5主5从 大型集群: 10主10从 适合: 数据量<100GB 适合: 数据量100GB-500GB 适合: 数据量>500GB
2.1.2 网络规划
网络要求:
- 所有节点之间网络互通
- 建议使用内网,避免公网延迟
- 节点间延迟建议<1ms
部署建议:
- 主从节点部署在不同物理机
- 避免主节点和从节点同时故障
- 考虑机架、机房分布
2.2 集群运维
2.2.1 日常监控
监控指标:
- 集群状态(cluster_state)
- 槽分配情况
- 节点状态
- 复制延迟
- 内存使用
- 网络流量
监控工具:
redis-cli --cluster checkCLUSTER INFOCLUSTER NODES- 第三方监控工具(Prometheus + Grafana)
2.2.2 故障处理
常见故障:
-
节点故障
- 检查节点状态
- 等待自动故障转移
- 修复故障节点后重新加入
-
网络分区
- 检查网络连通性
- 等待网络恢复
- 手动修复集群状态
-
槽未覆盖
- 检查槽分配
- 确保所有槽都有主节点
- 修复槽分配
2.2.3 备份与恢复
备份策略:
- 定期备份RDB文件
- 备份集群配置文件
- 记录集群拓扑信息
恢复步骤:
- 恢复RDB文件到各节点
- 恢复集群配置文件
- 重启集群节点
- 验证集群状态
三、总结
3.1 关键知识点回顾
-
哈希槽机制
- 16384个槽,均匀分配到主节点
- 使用CRC16算法计算键的槽号
- 支持键标签强制相关键在同一槽
-
Gossip协议
- 去中心化通信协议
- 节点间随机通信
- 最终一致性
-
数据路由
- 客户端计算槽号
- MOVED/ASK重定向
- 自动更新路由缓存
-
故障转移
- 自动检测节点故障
- 从节点选举新主节点
- 配置纪元解决冲突
-
扩容缩容
- 动态添加/移除节点
- 槽迁移过程
- 数据一致性保证
3.2 Cluster vs 哨兵模式对比
| 特性 | Redis Cluster | 哨兵模式 |
|---|---|---|
| 扩展性 | 水平扩展,支持大量节点 | 垂直扩展,节点数量有限 |
| 数据分片 | 自动分片 | 不分片 |
| 性能 | 高(多节点并行) | 中(单主节点) |
| 复杂度 | 高 | 中 |
| 适用场景 | 大型应用,大数据量 | 中小型应用 |
| 客户端支持 | 需要集群感知客户端 | 普通客户端+哨兵 |
3.3 最佳实践
-
部署建议
- 至少3主3从,6个节点
- 主从节点部署在不同物理机
- 使用高速内网连接
-
配置建议
- 合理设置
cluster-node-timeout - 使用键标签优化多键操作
- 监控集群状态和性能
- 合理设置
-
运维建议
- 定期检查集群健康状态
- 准备扩容/缩容预案
- 建立监控和告警机制
3.4 注意事项
-
功能限制
- 不支持多键操作(除非使用键标签)
- 不支持事务和Lua脚本(涉及多键)
- 需要集群感知的客户端
-
数据一致性
- 异步复制,存在延迟
- 网络分区可能导致数据不一致
- 合理设置
cluster-require-full-coverage
-
性能考虑
- 跨节点操作有网络开销
- 使用键标签减少跨节点操作
- 合理规划数据分布
下一篇预告: 第5篇将深入讲解Redis事务与Lua脚本,包括事务特性、WATCH机制、Lua脚本执行原理和原子性保证。