Redis 完全指南 / 11 - Redis Cluster 集群
Redis Cluster 集群
11.1 集群概述
Redis Cluster 是 Redis 官方的分布式解决方案,支持数据自动分片、高可用和水平扩展。
集群架构
┌─────────────────────────────────────────────────────────┐
│ Redis Cluster │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Master 1 │ │ Master 2 │ │ Master 3 │ │
│ │ Slot 0-5460 │ │Slot 5461-10922│ │Slot 10923-16383│ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ │
│ │Slave 1a │ │Slave 2a │ │Slave 3a │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────────────────┘
核心特性
| 特性 | 说明 |
|---|---|
| 数据分片 | 16384 个 Hash Slot,自动分配到不同节点 |
| 高可用 | 每个主节点可有 1-N 个从节点 |
| 自动故障转移 | 主节点故障时自动提升从节点 |
| 去中心化 | 所有节点都保存完整的集群信息 |
| 水平扩展 | 可动态添加/移除节点 |
11.2 分片原理
Hash Slot 机制
Redis Cluster 将所有数据分布到 16384 个 Hash Slot 中:
Key → CRC16(Key) % 16384 → Slot 编号 → 对应节点
# 计算 Key 的 Slot
redis-cli CLUSTER KEYSLOT "user:1001"
# (integer) 8106
redis-cli CLUSTER KEYSLOT "user:1002"
# (integer) 4807
Hash Tag(强制同一 Slot)
使用 {} 括起来的部分参与 Slot 计算,实现相关 Key 分配到同一节点:
# 使用 Hash Tag
redis-cli CLUSTER KEYSLOT "{user:1001}:name" # 与 {user:1001} 相同的 Slot
redis-cli CLUSTER KEYSLOT "{user:1001}:email" # 与 {user:1001} 相同的 Slot
# {user:1001} 参与 Slot 计算
redis-cli CLUSTER KEYSLOT "{user:1001}" # 8106
redis-cli CLUSTER KEYSLOT "{user:1001}:name" # 8106(相同 Slot!)
redis-cli CLUSTER KEYSLOT "{user:1001}:email" # 8106(相同 Slot!)
⚠️ 注意:如果使用 MGET/MSET/Pipeline,所有 Key 必须在同一个 Slot 中,否则报错:
# ❌ 错误:不同 Slot 的 Key 无法批量操作
MGET user:1001 user:1002
# (error) CROSSSLOT Keys in request don't hash to the same slot
# ✅ 正确:使用 Hash Tag 确保同一 Slot
MGET {user:1001}:name {user:1001}:email
11.3 搭建集群
Docker Compose 集群
version: '3.8'
services:
redis-node-1:
image: redis:7.2
container_name: redis-node-1
ports:
- "6371:6379"
- "16371:16379"
volumes:
- ./node-1/data:/data
command: >
redis-server
--cluster-enabled yes
--cluster-config-file nodes.conf
--cluster-node-timeout 5000
--appendonly yes
--port 6379
--cluster-announce-port 6371
--cluster-announce-bus-port 16371
networks:
- redis-cluster
redis-node-2:
image: redis:7.2
container_name: redis-node-2
ports:
- "6372:6379"
- "16372:16379"
volumes:
- ./node-2/data:/data
command: >
redis-server
--cluster-enabled yes
--cluster-config-file nodes.conf
--cluster-node-timeout 5000
--appendonly yes
--port 6379
--cluster-announce-port 6372
--cluster-announce-bus-port 16372
networks:
- redis-cluster
redis-node-3:
image: redis:7.2
container_name: redis-node-3
ports:
- "6373:6379"
- "16373:16379"
volumes:
- ./node-3/data:/data
command: >
redis-server
--cluster-enabled yes
--cluster-config-file nodes.conf
--cluster-node-timeout 5000
--appendonly yes
--port 6379
--cluster-announce-port 6373
--cluster-announce-bus-port 16373
networks:
- redis-cluster
redis-node-4:
image: redis:7.2
container_name: redis-node-4
ports:
- "6374:6379"
- "16374:16379"
volumes:
- ./node-4/data:/data
command: >
redis-server
--cluster-enabled yes
--cluster-config-file nodes.conf
--cluster-node-timeout 5000
--appendonly yes
--port 6379
--cluster-announce-port 6374
--cluster-announce-bus-port 16374
networks:
- redis-cluster
redis-node-5:
image: redis:7.2
container_name: redis-node-5
ports:
- "6375:6379"
- "16375:16379"
volumes:
- ./node-5/data:/data
command: >
redis-server
--cluster-enabled yes
--cluster-config-file nodes.conf
--cluster-node-timeout 5000
--appendonly yes
--port 6379
--cluster-announce-port 6375
--cluster-announce-bus-port 16375
networks:
- redis-cluster
redis-node-6:
image: redis:7.2
container_name: redis-node-6
ports:
- "6376:6379"
- "16376:16379"
volumes:
- ./node-6/data:/data
command: >
redis-server
--cluster-enabled yes
--cluster-config-file nodes.conf
--cluster-node-timeout 5000
--appendonly yes
--port 6379
--cluster-announce-port 6376
--cluster-announce-bus-port 16376
networks:
- redis-cluster
networks:
redis-cluster:
driver: bridge
创建集群
# 启动所有节点
docker-compose up -d
# 创建集群(3 主 3 从)
redis-cli --cluster create \
127.0.0.1:6371 \
127.0.0.1:6372 \
127.0.0.1:6373 \
127.0.0.1:6374 \
127.0.0.1:6375 \
127.0.0.1:6376 \
--cluster-replicas 1
# 输出类似:
# >>> Performing hash slots allocation on 6 nodes...
# Master[0] -> Slots 0 - 5460
# Master[1] -> Slots 5461 - 10922
# Master[2] -> Slots 10923 - 16383
# Adding replica 127.0.0.1:6375 to 127.0.0.1:6371
# Adding replica 127.0.0.1:6376 to 127.0.0.1:6372
# Adding replica 127.0.0.1:6374 to 127.0.0.1:6373
# >>> Trying to optimize slaves allocation for anti-affinity
# [OK] All 16384 slots covered.
11.4 集群操作命令
查看集群状态
# 连接集群
redis-cli -c -p 6371
# 查看集群信息
CLUSTER INFO
# 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
# 查看节点列表
CLUSTER NODES
# 输出每个节点的 ID、地址、角色、Slot 范围等
# 查看 Slot 分配
CLUSTER SLOTS
# 查看某个 Key 在哪个节点
CLUSTER KEYSLOT mykey
集群管理
# 添加节点
CLUSTER MEET 192.168.1.107 6379
# 添加从节点
CLUSTER REPLICATE <master-node-id>
# 忘记节点(移除节点)
CLUSTER FORGET <node-id>
# 重命名节点
CLUSTER SETNAME <name>
# 保存集群配置
CLUSTER SAVECONFIG
# 重置节点
CLUSTER RESET HARD/SOFT
11.5 扩缩容
添加节点
# 1. 启动新节点
redis-server --cluster-enabled yes --cluster-config-file nodes.conf --port 6377
# 2. 将新节点加入集群
redis-cli --cluster add-node 127.0.0.1:6377 127.0.0.1:6371
# 3. 重新分配 Slot(从现有节点迁移 Slot 到新节点)
redis-cli --cluster reshard 127.0.0.1:6371
# How many slots do you want to move (from 1 to 16384)? 4096
# What is the receiving node ID? <new-node-id>
# Source node #1: <old-node-id-1>
# Source node #2: <old-node-id-2>
# Source node #3: all ← 从所有现有节点平均分配
# 4. 添加为从节点(可选)
redis-cli -c -p 6377
> CLUSTER REPLICATE <master-node-id>
移除节点
# 1. 如果是主节点,先迁走所有 Slot
redis-cli --cluster reshard 127.0.0.1:6371
# 将该节点的 Slot 迁移到其他节点
# 2. 如果是从节点,先切换到其他主节点
# 3. 移除节点
redis-cli --cluster del-node 127.0.0.1:6371 <node-id>
在线扩缩容流程
在线扩容:
① 启动新节点
② 加入集群
③ 迁移 Slot(在线,不影响服务)
④ 添加从节点(可选)
在线缩容:
① 迁移 Slot 到其他节点
② 如果是主节点,先切换从节点
③ 移除节点
11.6 故障转移
自动故障转移
Master 1 (Slot 0-5460) 宕机
│
↓
Sentinel/Cluster 检测到故障
│
↓
Slave 1a 自动提升为新 Master
接管 Slot 0-5460
│
↓
客户端重定向到新 Master
手动故障转移
# 在从节点上执行手动故障转移
CLUSTER FAILOVER
# 强制故障转移(不等待主节点确认,可能丢数据)
CLUSTER FAILOVER FORCE
# 接管故障转移(当主节点已下线时使用)
CLUSTER FAILOVER TAKEOVER
11.7 集群路由
MOVED 重定向
客户端 → 节点 A(Key 不在本节点)
│
↓
MOVED 8106 192.168.1.102:6379
│
↓
客户端 → 节点 B(Key 所在节点)→ 返回结果
(客户端缓存 Slot 映射,下次直接访问正确节点)
ASK 重定向
Slot 迁移期间:
客户端 → 源节点(Key 已迁出)
│
↓
ASK 8106 192.168.1.103:6379
│
↓
客户端 → 目标节点(ASKING 命令 + 查询)
📌 业务场景
场景一:大规模缓存集群
# 6 节点集群(3 主 3 从)
# 总内存 3 × maxmemory
# 支持故障自动转移
# 数据自动分片
场景二:多数据中心
# 使用 cluster-announce-* 配置
# 不同数据中心使用不同的网络地址
场景三:弹性扩容
# 业务增长时在线添加节点
# 无需停机,自动迁移 Slot