Redis Cluster分片集群
Redis 的五种数据结构各有特色,用对了才能发挥它的优势。很多人只用到了 String 和 Hash,却不知道 List、Set、ZSet 在特定场景下更合适。本文从应用场景出发,讲什么时候用什么类型。
一、Cluster架构
#
1.1 基本架构
┌─────────┐ ┌─────────┐ ┌─────────┐ │ Master0 │<────>│ Master1 │<────>│ Master2 │ │ 0-5460 │ │ 5461-10922│ │10923-16383│ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │ Slave0 Slave1 Slave2
|
#
1.2 核心特点
- 数据分片:16384个哈希槽分配到各节点
- 无中心架构:节点间通过Gossip协议通信
- 自动故障转移:Slave可提升为Master
- 客户端直连:客户端缓存槽位映射,直接访问目标节点
二、哈希槽(Hash Slot)
#
2.1 哈希槽机制
Redis Cluster将key空间分为16384个槽(0-16383)
计算key对应的槽: slot = CRC16(key) % 16384
示例: key "user:1001" → CRC16("user:1001") % 16384 = 8523 key "order:2001" → CRC16("order:2001") % 16384 = 1234
|
#
2.2 槽位分配
6节点Cluster(3主3从):
Master A: slots 0-5460 (5461个槽) Master B: slots 5461-10922 (5462个槽) Master C: slots 10923-16383 (5461个槽)
Slave A 复制 Master A Slave B 复制 Master B Slave C 复制 Master C
|
#
2.3 Hash Tag
使用Hash Tag可以让相关key分配到同一槽:
key格式:{tag}:rest 只有tag部分参与计算slot
示例: {user}:1001:profile → 计算 {user} 的slot {user}:1001:orders → 同上,同一槽 {user}:1002:profile → 不同tag,不同槽
用途: - 保证相关数据在同一节点 - 支持跨key操作(如MGET、事务、Lua脚本)
|
三、节点通信
#
3.1 Gossip协议
每个节点每秒随机选择几个节点发送PING:
Node A ──PING──> Node B (携带自己知道的节点状态) Node B ──PONG──> Node A (携带自己知道的节点状态)
通过多次交换,每个节点最终知道整个集群的状态
|
PING/PONG消息内容:
- 发送节点的信息(id, ip, port, role, slots)
- 发送节点知道的其他节点信息(1/10随机选择)
- 发送节点标记为 FAIL 的节点列表
#
3.2 故障检测
Node A 发现 Node B 未响应PING │ ├── 标记 Node B 为 PFAIL(疑似故障) │ ├── 通过Gossip传播PFAIL信息 │ ├── 大多数Master节点都认为Node B是PFAIL │ └── 将PFAIL升级为FAIL(确认故障) └── 触发故障转移
|
四、Cluster配置
#
4.1 节点配置
# redis.conf (每个节点)
port 6379 cluster-enabled yes cluster-config-file nodes-6379.conf cluster-node-timeout 5000 cluster-require-full-coverage no
# 可选:保护模式 protected-mode no
# AOF持久化(推荐) appendonly yes
|
#
4.2 创建Cluster
redis-server conf/redis-6379.conf redis-server conf/redis-6380.conf redis-server conf/redis-6381.conf redis-server conf/redis-6382.conf redis-server conf/redis-6383.conf redis-server conf/redis-6384.conf
redis-cli --cluster create \ 192.168.1.100:6379 192.168.1.100:6380 192.168.1.100:6381 \ 192.168.1.100:6382 192.168.1.100:6383 192.168.1.100:6384 \ --cluster-replicas 1
|
#
4.3 查看Cluster信息
redis-cli -p 6379 CLUSTER NODES
redis-cli -p 6379 CLUSTER SLOTS
redis-cli -p 6379 CLUSTER INFO
|
五、请求路由
#
5.1 MOVED重定向
客户端发送:GET key │ ├── 计算slot = CRC16(key) % 16384 = 8523 │ ├── 客户端缓存的映射:8523 -> Node A │ └── 发送到Node A │ ├── Node A发现8523槽已迁移到Node B │ └── 返回:MOVED 8523 192.168.1.100:6380 客户端收到MOVED: ├── 更新缓存:8523 -> Node B └── 重定向到Node B
|
#
5.2 ASK重定向
槽正在迁移中:
源节点(Node A) 目标节点(Node B) │ │ ├── 部分数据已迁移到Node B ──>│ │ │ │ 客户端请求迁移中的key │ │ ─────────────────────────> │ │ │ ├── 如果key已迁移 │ │ └── 返回:ASK 8523 NodeB │ │ │ └── 如果key未迁移 │ └── 直接处理 │
ASK和MOVED的区别: - MOVED:槽已永久迁移,客户端应更新映射 - ASK:槽正在迁移,只是本次请求重定向
|
#
5.3 Smart Client
@Configuration public class RedisClusterConfig { @Bean public JedisCluster jedisCluster() { Set<HostAndPort> nodes = new HashSet<>(); nodes.add(new HostAndPort("192.168.1.100", 6379)); nodes.add(new HostAndPort("192.168.1.100", 6380)); nodes.add(new HostAndPort("192.168.1.100", 6381)); nodes.add(new HostAndPort("192.168.1.100", 6382)); nodes.add(new HostAndPort("192.168.1.100", 6383)); nodes.add(new HostAndPort("192.168.1.100", 6384)); JedisPoolConfig poolConfig = new JedisPoolConfig(); poolConfig.setMaxTotal(100); return new JedisCluster(nodes, 5000, 5000, 3, "password", poolConfig); } }
@Autowired private JedisCluster jedisCluster;
public void setValue(String key, String value) { jedisCluster.set(key, value); }
|
#
5.4 Spring Boot配置
spring: redis: cluster: nodes: - 192.168.1.100:6379 - 192.168.1.100:6380 - 192.168.1.100:6381 - 192.168.1.100:6382 - 192.168.1.100:6383 - 192.168.1.100:6384 max-redirects: 3 password: password lettuce: pool: max-active: 100
|
六、扩缩容
#
6.1 添加新节点
redis-server conf/redis-6385.conf
redis-cli --cluster add-node 192.168.1.100:6385 192.168.1.100:6379
redis-cli --cluster reshard 192.168.1.100:6379
|
#
6.2 添加Slave
redis-cli --cluster add-node 192.168.1.100:6386 192.168.1.100:6379 \ --cluster-slave --cluster-master-id <master-id>
|
#
6.3 删除节点
redis-cli --cluster reshard 192.168.1.100:6379
redis-cli --cluster del-node 192.168.1.100:6379 <node-id>
|
#
6.4 槽位迁移流程
迁移slot 8523从Node A到Node B:
1. Node A设置slot 8523为MIGRATING状态 2. Node B设置slot 8523为IMPORTING状态
3. 对slot 8523中的每个key: a. 从Node A获取key(DUMP) b. 发送到Node B(RESTORE) c. 从Node A删除key
4. 所有key迁移完成后: a. 将slot 8523分配给Node B b. 通过Gossip传播新配置
|
七、故障转移
#
7.1 自动故障转移
Master A故障 │ ├── 其他Master通过Gossip发现A故障 │ ├── 标记A为FAIL(需要多数Master同意) │ ├── Slave A发起选举 │ (向其他Master请求投票) │ ├── 获得多数票 → 提升为Master │ ├── 接管Master A的槽位 │ └── 通过Gossip通知所有节点
|
#
7.2 手动故障转移
redis-cli -p 6382 CLUSTER FAILOVER
redis-cli -p 6382 CLUSTER FAILOVER FORCE
redis-cli -p 6382 CLUSTER FAILOVER TAKEOVER
|
八、Cluster限制
#
8.1 跨槽操作限制
MGET key1 key2 MSET key1 v1 key2 v2 RENAME key1 key2
MGET {user}:1001:name {user}:1001:age
|
#
8.2 事务和Lua脚本限制
MULTI SET {user}:1001:name "Alice" SET {user}:1001:age 25 EXEC
EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 {user}:1001:name Alice
|
#
8.3 多key操作限制
String userTag = "{user:1001}"; redis.mset( userTag + ":name", "Alice", userTag + ":age", "25" );
|
九、常见问题
#
9.1 CLUSTERDOWN错误
redis-cli CONFIG SET cluster-require-full-coverage no
|
#
9.2 脑裂问题
# 配置最小Slave数,防止脑裂 min-replicas-to-write 1 min-replicas-max-lag 10
|
#
9.3 大数据量迁移慢
redis-cli CONFIG SET cluster-migration-barrier 1
redis-cli --cluster reshard --pipeline 100 ...
|
十、总结
| 特性 |
说明 |
| 哈希槽 |
16384个槽,CRC16(key) % 16384 |
| 数据分片 |
槽分配到各Master节点 |
| 通信协议 |
Gossip,去中心化 |
| 故障检测 |
PFAIL → FAIL,多数同意 |
| 故障转移 |
Slave选举,自动提升 |
| 客户端 |
Smart Client,缓存槽映射 |
| 扩缩容 |
槽迁移,在线进行 |
| 场景 |
方案 |
| 数据量 < 10GB |
单实例或Sentinel |
| 数据量 > 10GB |
Cluster |
| 需要水平扩展 |
Cluster |
| 多key操作多 |
使用Hash Tag或Sentinel |
Redis Cluster是Redis水平扩展的标准方案,适合大数据量和高并发场景。但需要注意跨槽操作的限制,合理设计key的Hash Tag。
核心要点
String:简单的键值对,适合缓存、计数器
Hash:存储对象属性,适合用户信息、配置
List:有序列表,适合消息队列、最新列表
Set:无序去重,适合共同好友、抽奖
ZSet:有序集合,适合排行榜、积分系统
总结
选择合适的数据结构是使用 Redis 的关键。在实际项目中,根据业务需求选择合适的类型,可以提升性能和开发效率。