Redis Cluster分片集群

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

# 1. 启动6个节点
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

# 2. 创建Cluster
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

# --cluster-replicas 1: 每个Master有1个Slave

#

4.3 查看Cluster信息

# 查看节点信息
redis-cli -p 6379 CLUSTER NODES

# 输出:
# <id> <ip:port>@<cport> <flags> <master_id> <ping_sent> <pong_recv> <epoch> <link_state> <slot>
# a1b2... 192.168.1.100:6379@16379 myself,master - 0 1625097600 1 connected 0-5460
# c3d4... 192.168.1.100:6380@16380 master - 0 1625097601 2 connected 5461-10922
# e5f6... 192.168.1.100:6381@16381 master - 0 1625097602 3 connected 10923-16383
# g7h8... 192.168.1.100:6382@16382 slave a1b2... 0 1625097603 1 connected
# ...

# 查看槽位分配
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

// Jedis Cluster(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) {
// 客户端自动计算slot并路由到正确节点
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 添加新节点

# 1. 启动新节点
redis-server conf/redis-6385.conf

# 2. 添加为Master
redis-cli --cluster add-node 192.168.1.100:6385 192.168.1.100:6379

# 3. 分配槽位(从现有节点迁移)
redis-cli --cluster reshard 192.168.1.100:6379
# 输入要迁移的槽数:4096
# 输入目标节点ID:新节点ID
# 输入源节点ID:all(从所有节点平均迁移)

#

6.2 添加Slave

# 添加Slave到指定Master
redis-cli --cluster add-node 192.168.1.100:6386 192.168.1.100:6379 \
--cluster-slave --cluster-master-id <master-id>

#

6.3 删除节点

# 1. 先迁移该节点的槽位到其他节点
redis-cli --cluster reshard 192.168.1.100:6379
# 将所有槽迁移走

# 2. 删除节点
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 手动故障转移

# 在Slave上执行,安全切换(零数据丢失)
redis-cli -p 6382 CLUSTER FAILOVER

# 强制故障转移(可能丢失少量数据)
redis-cli -p 6382 CLUSTER FAILOVER FORCE

# 强制接管(用于原Master完全不可用)
redis-cli -p 6382 CLUSTER FAILOVER TAKEOVER

八、Cluster限制

#

8.1 跨槽操作限制

# 以下命令要求所有key在同一槽:
MGET key1 key2 # 如果key1和key2在不同槽,报错
MSET key1 v1 key2 v2 # 同上
RENAME key1 key2 # 同上

# 解决方案:使用Hash Tag
MGET {user}:1001:name {user}:1001:age

#

8.2 事务和Lua脚本限制

# 事务中的key必须在同一槽
MULTI
SET {user}:1001:name "Alice"
SET {user}:1001:age 25
EXEC

# Lua脚本中的key必须在同一槽
EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 {user}:1001:name Alice

#

8.3 多key操作限制

// 使用Hash Tag确保相关key在同一槽
String userTag = "{user:1001}";
redis.mset(
userTag + ":name", "Alice",
userTag + ":age", "25"
);

九、常见问题

#

9.1 CLUSTERDOWN错误

# 如果配置cluster-require-full-coverage yes
# 当部分槽不可用时,返回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

# 使用pipeline加速迁移
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。

核心要点

  1. String:简单的键值对,适合缓存、计数器

  2. Hash:存储对象属性,适合用户信息、配置

  3. List:有序列表,适合消息队列、最新列表

  4. Set:无序去重,适合共同好友、抽奖

  5. ZSet:有序集合,适合排行榜、积分系统

总结

选择合适的数据结构是使用 Redis 的关键。在实际项目中,根据业务需求选择合适的类型,可以提升性能和开发效率。


   转载规则


《Redis Cluster分片集群》 小乐 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录