Redis Cluster 集群
2026/3/20大约 17 分钟
Redis Cluster 集群
一、Cluster 概述
1.1 为什么需要 Cluster
Sentinel 虽然解决了高可用问题,但仍有以下限制:
- 容量限制:数据存储在单个主节点,受单机内存限制
- 写入瓶颈:所有写入都由单个主节点处理
- 无法水平扩展:只能通过增加从节点扩展读能力
Redis Cluster 是 Redis 官方的分布式解决方案(Redis 3.0+),提供:
- 数据分片:数据自动分布到多个节点
- 高可用:内置故障检测和自动故障转移
- 线性扩展:可以动态添加/删除节点
1.2 Cluster 架构
1.3 核心概念
| 概念 | 说明 |
|---|---|
| 槽(Slot) | 数据分片的基本单位,共 16384 个槽 |
| 节点 | 集群中的 Redis 实例 |
| 主节点 | 负责处理槽的读写,持有数据 |
| 从节点 | 复制主节点数据,主节点故障时可提升 |
| Gossip 协议 | 节点间通信协议,用于状态传播 |
| MOVED | 重定向响应,表示 key 在其他节点 |
| ASK | 临时重定向,用于槽迁移过程 |
二、数据分片原理
2.1 哈希槽(Hash Slot)
Redis Cluster 使用 CRC16 算法计算 key 的哈希槽:
槽位计算公式
slot = CRC16(key) % 16384
示例:
key = "user:1001"
CRC16("user:1001") = 49876
slot = 49876 % 16384 = 724
key "user:1001" 应该存储在负责槽位 724 的节点上
2.2 Hash Tag
如果需要将多个 key 存储在同一个槽(例如事务操作),可以使用 Hash Tag:
# 只有 {} 中的部分参与哈希计算
# 以下 key 都会分配到同一个槽
SET {user:1001}:name "张三"
SET {user:1001}:age "25"
SET {user:1001}:email "zhangsan@example.com"
# slot = CRC16("user:1001") % 16384
# 都在同一个槽,可以在同一个节点上执行事务
# 使用场景:
# 1. 需要对多个 key 执行 MGET/MSET
# 2. 需要使用 Lua 脚本操作多个 key
# 3. 需要使用事务操作多个 key
2.3 槽位分配
# 查看槽位分配
redis-cli -c -p 7001 CLUSTER SLOTS
# 输出示例:
# 1) 1) (integer) 0 # 槽起始
# 2) (integer) 5460 # 槽结束
# 3) 1) "192.168.1.10" # 主节点 IP
# 2) (integer) 7001 # 主节点端口
# 3) "id..." # 节点 ID
# 4) 1) "192.168.1.10" # 从节点
# 2) (integer) 7004
# 3) "id..."
# 2) 1) (integer) 5461
# 2) (integer) 10922
# ...
# 查看 key 的槽位
redis-cli -c -p 7001 CLUSTER KEYSLOT "user:1001"
# (integer) 724
# 查看槽位中的 key 数量
redis-cli -c -p 7001 CLUSTER COUNTKEYSINSLOT 724
三、集群部署
3.1 环境准备
创建 6 个 Redis 实例(3 主 3 从):
| 节点 | IP | 端口 | 角色 |
|---|---|---|---|
| Node 1 | 192.168.1.10 | 7001 | Master |
| Node 2 | 192.168.1.10 | 7002 | Master |
| Node 3 | 192.168.1.10 | 7003 | Master |
| Node 4 | 192.168.1.10 | 7004 | Slave |
| Node 5 | 192.168.1.10 | 7005 | Slave |
| Node 6 | 192.168.1.10 | 7006 | Slave |
3.2 配置文件
每个节点的配置文件(以 7001 为例):
# redis-7001.conf
# 基础配置
port 7001
bind 0.0.0.0
daemonize yes
pidfile /var/run/redis_7001.pid
logfile /var/log/redis/redis_7001.log
dir /data/redis/7001
# 密码(集群中所有节点密码必须一致)
requirepass cluster_password
masterauth cluster_password
# 开启集群模式
cluster-enabled yes
# 集群配置文件(自动生成和维护)
cluster-config-file nodes-7001.conf
# 节点超时时间(毫秒)
cluster-node-timeout 15000
# 从节点故障转移有效因子
# 超时时间 * 此因子 = 从节点认为主节点故障的时间
cluster-replica-validity-factor 10
# 主节点需要的最小从节点数
# 0 表示即使没有从节点也继续服务
cluster-require-full-coverage yes
# 允许从节点在没有主节点时迁移
cluster-allow-replica-migration yes
# 持久化
appendonly yes
appendfsync everysec
# 内存配置
maxmemory 2gb
maxmemory-policy allkeys-lru
3.3 创建集群
方式一:使用 redis-cli(推荐)
# 1. 启动所有节点
for port in 7001 7002 7003 7004 7005 7006; do
redis-server /etc/redis/redis-${port}.conf
done
# 2. 创建集群
# --cluster-replicas 1 表示每个主节点有 1 个从节点
redis-cli --cluster create \
192.168.1.10:7001 \
192.168.1.10:7002 \
192.168.1.10:7003 \
192.168.1.10:7004 \
192.168.1.10:7005 \
192.168.1.10:7006 \
--cluster-replicas 1 \
-a cluster_password
# 交互确认后输入 yes
# 3. 验证集群状态
redis-cli -c -p 7001 -a cluster_password CLUSTER INFO
# cluster_state:ok
# cluster_slots_assigned:16384
# cluster_slots_ok:16384
# cluster_known_nodes:6
# cluster_size:3
# 4. 查看节点信息
redis-cli -c -p 7001 -a cluster_password CLUSTER NODES
方式二:手动创建
# 1. 启动所有节点(同上)
# 2. 节点握手(让所有节点互相认识)
redis-cli -p 7001 CLUSTER MEET 192.168.1.10 7002
redis-cli -p 7001 CLUSTER MEET 192.168.1.10 7003
redis-cli -p 7001 CLUSTER MEET 192.168.1.10 7004
redis-cli -p 7001 CLUSTER MEET 192.168.1.10 7005
redis-cli -p 7001 CLUSTER MEET 192.168.1.10 7006
# 3. 分配槽位
redis-cli -p 7001 CLUSTER ADDSLOTS {0..5460}
redis-cli -p 7002 CLUSTER ADDSLOTS {5461..10922}
redis-cli -p 7003 CLUSTER ADDSLOTS {10923..16383}
# 4. 设置主从关系
# 获取主节点 ID
redis-cli -p 7001 CLUSTER MYID
# 假设返回:abc123...
# 设置从节点
redis-cli -p 7004 CLUSTER REPLICATE abc123...
redis-cli -p 7005 CLUSTER REPLICATE <7002的ID>
redis-cli -p 7006 CLUSTER REPLICATE <7003的ID>
3.4 Docker 部署
# docker-compose.yml
version: "3"
services:
redis-node-1:
image: redis:6.2
container_name: redis-node-1
command: redis-server /etc/redis/redis.conf
ports:
- "7001:7001"
- "17001:17001"
volumes:
- ./conf/redis-7001.conf:/etc/redis/redis.conf
- ./data/7001:/data
networks:
redis-cluster:
ipv4_address: 172.28.0.11
redis-node-2:
image: redis:6.2
container_name: redis-node-2
command: redis-server /etc/redis/redis.conf
ports:
- "7002:7002"
- "17002:17002"
volumes:
- ./conf/redis-7002.conf:/etc/redis/redis.conf
- ./data/7002:/data
networks:
redis-cluster:
ipv4_address: 172.28.0.12
# ... 其他节点配置类似
networks:
redis-cluster:
driver: bridge
ipam:
config:
- subnet: 172.28.0.0/16
四、节点通信
4.1 Gossip 协议
Redis Cluster 使用 Gossip 协议进行节点间通信:
4.2 消息类型
| 消息类型 | 说明 |
|---|---|
| MEET | 新节点加入,发送者通知接收者加入集群 |
| PING | 心跳检测,检查节点是否存活 |
| PONG | 回复 PING/MEET,包含自身状态信息 |
| FAIL | 广播某节点已下线 |
| PUBLISH | 向集群所有节点广播消息 |
4.3 集群端口
每个节点使用两个端口:
- 客户端端口:如 7001,处理客户端命令
- 集群总线端口:客户端端口 + 10000,如 17001,节点间通信
# 确保防火墙开放两个端口
# iptables -A INPUT -p tcp --dport 7001 -j ACCEPT
# iptables -A INPUT -p tcp --dport 17001 -j ACCEPT
五、请求路由
5.1 MOVED 重定向
当客户端请求的 key 不在当前节点时,节点返回 MOVED 错误:
5.2 ASK 重定向
在槽迁移过程中,可能返回 ASK 错误:
5.3 客户端连接
使用 redis-cli
# 不带 -c 参数:收到 MOVED 会报错
redis-cli -p 7001 -a password GET user:1001
# (error) MOVED 724 192.168.1.10:7002
# 带 -c 参数:自动重定向
redis-cli -c -p 7001 -a password GET user:1001
# -> Redirected to slot [724] located at 192.168.1.10:7002
# "张三"
六、客户端连接
6.1 Jedis 连接 Cluster
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;
public class JedisClusterExample {
public static void main(String[] args) {
// 集群节点
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("192.168.1.10", 7001));
nodes.add(new HostAndPort("192.168.1.10", 7002));
nodes.add(new HostAndPort("192.168.1.10", 7003));
// 连接池配置
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(100);
poolConfig.setMaxIdle(20);
poolConfig.setMinIdle(5);
// 创建集群连接
JedisCluster cluster = new JedisCluster(
nodes,
2000, // 连接超时
2000, // 读取超时
5, // 最大重试次数
"cluster_password", // 密码
poolConfig
);
// 使用
cluster.set("key", "value");
String value = cluster.get("key");
System.out.println("Value: " + value);
// 关闭
cluster.close();
}
}
6.2 Lettuce 连接 Cluster
import io.lettuce.core.RedisURI;
import io.lettuce.core.cluster.RedisClusterClient;
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
public class LettuceClusterExample {
public static void main(String[] args) {
// 创建 URI(只需要提供部分节点,客户端会自动发现其他节点)
List<RedisURI> uris = Arrays.asList(
RedisURI.builder()
.withHost("192.168.1.10")
.withPort(7001)
.withPassword("cluster_password".toCharArray())
.build(),
RedisURI.builder()
.withHost("192.168.1.10")
.withPort(7002)
.withPassword("cluster_password".toCharArray())
.build()
);
// 创建客户端
RedisClusterClient client = RedisClusterClient.create(uris);
// 获取连接
StatefulRedisClusterConnection<String, String> connection = client.connect();
RedisAdvancedClusterCommands<String, String> commands = connection.sync();
// 使用
commands.set("key", "value");
String value = commands.get("key");
System.out.println("Value: " + value);
// 关闭
connection.close();
client.shutdown();
}
}
6.3 Spring Boot 配置
# application.yml
spring:
redis:
cluster:
nodes:
- 192.168.1.10:7001
- 192.168.1.10:7002
- 192.168.1.10:7003
max-redirects: 3
password: cluster_password
timeout: 3000ms
lettuce:
pool:
max-active: 100
max-idle: 20
min-idle: 5
max-wait: 3000ms
6.4 Python 连接 Cluster
from rediscluster import RedisCluster
# 启动节点(只需要提供部分节点)
startup_nodes = [
{"host": "192.168.1.10", "port": "7001"},
{"host": "192.168.1.10", "port": "7002"},
{"host": "192.168.1.10", "port": "7003"}
]
# 创建连接
rc = RedisCluster(
startup_nodes=startup_nodes,
decode_responses=True,
password='cluster_password'
)
# 使用
rc.set("key", "value")
value = rc.get("key")
print(f"Value: {value}")
七、集群运维
7.1 添加节点
# 添加主节点
redis-cli --cluster add-node \
192.168.1.10:7007 \
192.168.1.10:7001 \
-a cluster_password
# 新节点加入后没有分配槽位,需要重新分片
# 添加从节点
redis-cli --cluster add-node \
192.168.1.10:7008 \
192.168.1.10:7001 \
--cluster-slave \
--cluster-master-id <master-node-id> \
-a cluster_password
7.2 删除节点
# 1. 如果是主节点,先将槽迁移到其他节点
redis-cli --cluster reshard 192.168.1.10:7001 \
--cluster-from <node-id> \
--cluster-to <target-node-id> \
--cluster-slots <num> \
-a cluster_password
# 2. 删除节点
redis-cli --cluster del-node \
192.168.1.10:7001 \
<node-id> \
-a cluster_password
7.3 槽位迁移(Reshard)
# 交互式迁移
redis-cli --cluster reshard 192.168.1.10:7001 -a cluster_password
# 按提示输入:
# 1. 要迁移的槽数量
# 2. 目标节点 ID
# 3. 源节点 ID(或 all)
# 非交互式迁移
redis-cli --cluster reshard 192.168.1.10:7001 \
--cluster-from <source-node-id> \
--cluster-to <target-node-id> \
--cluster-slots 1000 \
--cluster-yes \
-a cluster_password
7.4 故障转移
# 手动故障转移(在从节点执行)
redis-cli -c -p 7004 -a password CLUSTER FAILOVER
# 强制故障转移(主节点可能不可用)
redis-cli -c -p 7004 -a password CLUSTER FAILOVER FORCE
# 接管(主节点确定不可用)
redis-cli -c -p 7004 -a password CLUSTER FAILOVER TAKEOVER
7.5 集群检查与修复
# 检查集群状态
redis-cli --cluster check 192.168.1.10:7001 -a password
# 修复集群
redis-cli --cluster fix 192.168.1.10:7001 -a password
# 重新平衡槽位
redis-cli --cluster rebalance 192.168.1.10:7001 \
--cluster-threshold 1 \
-a password
# 获取集群信息
redis-cli --cluster info 192.168.1.10:7001 -a password
八、故障检测与转移
8.1 故障检测
8.2 故障转移
8.3 从节点选举优先级
当多个从节点竞争成为新主节点时,优先级规则:
九、集群限制
9.1 不支持的操作
# 1. 多 key 操作,key 必须在同一个槽
MGET key1 key2 key3 # 如果 key 在不同槽,报错
# 解决方案:使用 Hash Tag
MGET {user}:key1 {user}:key2 {user}:key3
# 2. 事务中的 key 必须在同一个槽
MULTI
SET key1 value1
SET key2 value2 # 如果 key1 和 key2 在不同槽,报错
EXEC
# 3. 不支持 SELECT 命令(只有 db0)
SELECT 1 # 报错
# 4. Lua 脚本中的 key 必须在同一个槽
EVAL "redis.call('SET', KEYS[1], ARGV[1]); redis.call('SET', KEYS[2], ARGV[2])" 2 key1 key2 val1 val2
# 如果 key1 和 key2 在不同槽,报错
9.2 性能考虑
十、最佳实践
10.1 部署建议
| 建议 | 说明 |
|---|---|
| 主节点数量 | 至少 3 个,推荐奇数个 |
| 从节点数量 | 每个主节点至少 1 个从节点 |
| 部署分布 | 主从节点分布在不同机器/机架 |
| 内存预留 | 预留 30% 用于 fork 和缓冲 |
| 槽位均匀 | 确保槽位在节点间均匀分布 |
10.2 Key 设计
# 推荐:使用 Hash Tag 关联相关 key
{user:1001}:info
{user:1001}:orders
{user:1001}:cart
# 避免:热点 key
# 所有请求都落到同一个节点,造成负载不均
# 避免:大 key
# 单个 key 过大会影响迁移和同步
10.3 监控指标
# 集群状态
redis-cli -c -p 7001 CLUSTER INFO
# 关键指标:
# cluster_state: ok/fail
# cluster_slots_assigned: 16384
# cluster_slots_ok: 16384
# cluster_known_nodes: 6
# cluster_size: 3
# 节点信息
redis-cli -c -p 7001 CLUSTER NODES
# 检查槽位覆盖
redis-cli --cluster check 192.168.1.10:7001
十一、总结
Cluster 核心要点
- 16384 个槽:数据分片的基本单位
- Gossip 协议:节点间状态同步
- MOVED/ASK:请求重定向机制
- 自动故障转移:无需外部组件
与 Sentinel 对比
| 对比项 | Sentinel | Cluster |
|---|---|---|
| 数据分片 | 不支持 | 支持 |
| 容量 | 单机限制 | 可扩展 |
| 故障转移 | 外部 Sentinel | 内置 |
| 复杂度 | 较低 | 较高 |
| 多 key 操作 | 支持 | 受限 |
选择建议
- 数据量小(<10GB):Sentinel 即可
- 数据量大、高并发:Cluster
- 需要多 key 事务:Sentinel 或 Hash Tag
- 水平扩展需求:Cluster