Redis学习笔记:Cluster 分片集群从原理到实战部署

一、什么是 Redis Cluster

Redis Cluster 是 Redis 官方提供的分布式解决方案,从 Redis 3.0 开始引入。它通过数据分片(Sharding)自动故障转移(Failover),实现了:

  • 水平扩展:将数据自动分布到多个节点
  • 高可用性:部分节点故障时集群仍可正常工作
  • 去中心化架构:无中心代理节点,所有节点通过 Gossip 协议通信

解决了什么问题

问题 说明
单机容量瓶颈 单个 Redis 实例最多存储几个 GB 到几十 GB 数据,Cluster 可以将数据分散到多台机器
单点故障 主节点宕机后,从节点自动晋升,无需人工干预
读写压力 多主节点并行处理请求,写入吞吐量线性提升
扩容困难 传统方式需要停机迁移数据,Cluster 支持在线动态扩缩容

二、Redis 部署方案对比

特性 单机实例 主从复制 Sentinel 哨兵 Cluster 分片集群
数据容量 受单机内存限制 受单机内存限制 受单机内存限制 可扩展至多台机器
写入性能 单点写入 单点写入(主) 单点写入(主) 多主写入,线性扩展
自动故障转移
自动分片
在线扩缩容
配置复杂度 ⭐ 简单 ⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐
适用场景 开发/缓存 读写分离 高可用场景 大规模高可用场景

什么时候选择 Cluster?

  • 数据量超过单机内存(建议单机 ≤ 16GB)
  • 需要高写入吞吐量
  • 要求自动故障转移高可用性
  • 希望在线扩缩容,避免停机

注意:如果数据量小且不需要高可用,单机或 Sentinel 更简单高效。Cluster 的事务和 Lua 脚本仅支持同一槽位的 Key。


三、核心架构原理

3.1 哈希槽(Hash Slot)

Redis Cluster 没有采用一致性哈希,而是使用固定 16384 个哈希槽(Hash Slot):

CRC16(key) % 16384 → 所属槽位
  • 每个 Key 通过 CRC16 算法计算出哈希值,对 16384 取模得到槽号
  • 每个节点负责一段连续的槽位范围
  • 新增/移除节点时,只需迁移槽位,不需要 rehash 所有数据

为什么是 16384 个槽?

Redis 作者在 FAQ 中解释过:16384 个槽用 2KB 的空间即可在节点间传递完整的槽位信息(16384 bit ÷ 8 ÷ 1024 = 2KB),而 65535 个槽则需要 8KB,在网络开销和灵活性之间取得了平衡。

3.2 Gossip 协议

Cluster 节点间通过 Gossip(八卦)协议 通信,每个节点:

  • 每秒随机选择 5 个节点发送 PING
  • 交换节点状态、槽位信息、主从关系
  • 如果收到 PONG 超时,标记节点为 PFAIL(可能故障)
  • 多数主节点都标记某个节点为 PFAIL,升级为 FAIL 并触发故障转移

这种去中心化设计意味着没有单点瓶颈,但最终一致性意味着短时间内节点间可能存在信息不一致。

3.3 主从架构

每个主节点(Master)可以有 1 个或多个从节点(Slave):

  • 主节点:负责处理读写请求和槽位数据
  • 从节点:异步复制主节点数据,主节点宕机时选举晋升
  • 建议为每个主节点至少配置一个从节点,保证高可用

四、环境搭建:Docker Compose

下面我们用 Docker Compose 启动 6 个 Redis 节点(3 主 3 从),这是生产推荐的最小集群配置。

version: '3.8'

services:
  redis-node-1:
    image: redis:7.2.4
    container_name: redis-cluster-1
    command: redis-server --port 6379 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --bind 0.0.0.0
    ports:
      - "6379:6379"
      - "16379:16379"
    networks:
      - cluster-net

  redis-node-2:
    image: redis:7.2.4
    container_name: redis-cluster-2
    command: redis-server --port 6379 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --bind 0.0.0.0
    ports:
      - "6380:6379"
      - "16380:16379"
    networks:
      - cluster-net

  redis-node-3:
    image: redis:7.2.4
    container_name: redis-cluster-3
    command: redis-server --port 6379 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --bind 0.0.0.0
    ports:
      - "6381:6379"
      - "16381:16379"
    networks:
      - cluster-net

  redis-node-4:
    image: redis:7.2.4
    container_name: redis-cluster-4
    command: redis-server --port 6379 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --bind 0.0.0.0
    ports:
      - "6382:6379"
      - "16382:16379"
    networks:
      - cluster-net

  redis-node-5:
    image: redis:7.2.4
    container_name: redis-cluster-5
    command: redis-server --port 6379 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --bind 0.0.0.0
    ports:
      - "6383:6379"
      - "16383:16379"
    networks:
      - cluster-net

  redis-node-6:
    image: redis:7.2.4
    container_name: redis-cluster-6
    command: redis-server --port 6379 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes --bind 0.0.0.0
    ports:
      - "6384:6379"
      - "16384:16379"
    networks:
      - cluster-net

networks:
  cluster-net:
    driver: bridge

配置参数详解

参数 含义
--cluster-enabled yes 启用集群模式
--cluster-config-file nodes.conf 集群自动生成的配置文件(不需要手动创建)
--cluster-node-timeout 5000 节点超时时间(ms),超时被视为故障
--appendonly yes 开启 AOF 持久化
--bind 0.0.0.0 允许来自所有网络接口的连接
16379 端口 集群总线端口(数据端口 + 10000),用于节点间 Gossip 通信

五、创建分片集群

5.1 启动容器

docker-compose up -d

5.2 创建集群

进入任意一个容器并执行创建命令:

# 进入容器
docker exec -it redis-cluster-1 sh

# 创建集群(注意这里用的是容器名,不是 IP!)
redis-cli --cluster create \
  redis-cluster-1:6379 \
  redis-cluster-2:6379 \
  redis-cluster-3:6379 \
  redis-cluster-4:6379 \
  redis-cluster-5:6379 \
  redis-cluster-6:6379 \
  --cluster-replicas 1

命令解析

  • --cluster-replicas 1:每个主节点分配 1 个从节点,总共 6 个节点 → 3 主 3 从
  • 如果不加这个参数,6 个节点全为主节点(不推荐,无高可用)

执行后 Redis 会打印槽位分配方案并让你确认,输入 yes 继续。

5.3 参考输出

>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica redis-cluster-4:6379 to redis-cluster-1:6379
Adding replica redis-cluster-5:6379 to redis-cluster-2:6379
Adding replica redis-cluster-6:6379 to redis-cluster-3:6379
...
[OK] All 16384 slots covered.

看到 [OK] All 16384 slots covered. 表示所有 16384 个槽位分配完毕,集群创建成功。


六、验证集群状态

6.1 查看节点信息

docker exec -it redis-cluster-1 redis-cli cluster nodes

输出示例:

16beb014f42bf997e7e9dc40e4a03d96588ca2f7 172.23.0.5:6379@16379 master - 0 1784104659539 2 connected 5461-10922
c2c4b89c1359f8b7fc200fb9fb3f5e591132461c 172.23.0.4:6379@16379 myself,master - 0 1784104658000 1 connected 0-5460
ff93232a5b0ce6abfd4c04dad4e778e47cf19d45 172.23.0.2:6379@16379 slave 16beb014f42bf997e7e9dc40e4a03d96588ca2f7 0 1784104659000 2 connected
d098439d4b4ad4a461777737e7ea2ab2d9591d1b 172.23.0.7:6379@16379 slave c2c4b89c1359f8b7fc200fb9fb3f5e591132461c 0 1784104659640 1 connected
5089042042bcc6ad2edf0b6dc07d5721c0c4fcf5 172.23.0.6:6379@16379 slave 7556fed402a74a5dd377365e16d41253d2e1ee99 0 1784104658000 3 connected
7556fed402a74a5dd377365e16d41253d2e1ee99 172.23.0.3:6379@16379 master - 0 1784104659000 3 connected 10923-16383

字段解读

字段 示例 含义
节点 ID 16beb0... 节点唯一标识(持久化到 nodes.conf)
地址 172.23.0.5:6379@16379 数据端口@集群总线端口
角色 master / slave 主节点或从节点
主节点 ID 16beb0...(slave 行) 该从节点所属的主节点
连接状态 myself,master 当前连接的节点是自己
槽位范围 0-5460 该节点负责的哈希槽范围

6.2 集群完整性检查

docker exec -it redis-cluster-1 redis-cli --cluster check redis-cluster-1:6379

输出:

[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

七、数据读写与 MOVED 重定向

7.1 不使用集群模式

docker exec -it redis-cluster-1 redis-cli set foo bar

返回:

(error) MOVED 12182 172.23.0.3:6379

为什么会这样?

这个错误是 Redis Cluster 的典型行为

  1. 客户端向 redis-cluster-1 发送 SET foo bar
  2. Redis 对 foo 计算哈希槽:CRC16("foo") % 16384 = 12182
  3. redis-cluster-1 不负责 12182 号槽(它负责 0-5460)
  4. 它知道负责该槽的节点是 172.23.0.3:6379,返回 MOVED 错误
  5. 客户端应该重定向到正确节点执行命令

7.2 使用集群模式(-c 参数)

# 写入
docker exec -it redis-cluster-1 redis-cli -c set foo bar

# 读取(不需要在特定节点读)
docker exec -it redis-cluster-3 redis-cli -c get foo

-c(cluster 模式)让 redis-cli 自动跟随 MOVED 重定向,透明地连接到正确的节点执行命令。

生产建议:应用代码中应使用支持 Cluster 的 Redis 客户端(如 redis-py-clusterLettuceJedisCluster),它们会自动处理 MOVED 重定向和槽位缓存。

7.3 查看 Key 的哈希槽

docker exec -it redis-cluster-1 redis-cli CLUSTER KEYSLOT foo
# 返回: (integer) 12182

7.4 查询所有槽位分布

docker exec -it redis-cluster-1 redis-cli CLUSTER SLOTS

输出示例:

1) 1) (integer) 0          <-- 起始槽位
   2) (integer) 5460       <-- 结束槽位
   3) 1) "172.23.0.2"      <-- 主节点 IP
      2) (integer) 6379
      3) "node_id_1"
   4) 1) "172.23.0.5"      <-- 从节点 IP
      2) (integer) 6379
      3) "node_id_4"
2) 1) (integer) 5461
   2) (integer) 10922
   3) 1) "172.23.0.3"
      2) (integer) 6379
      ...

八、故障转移

8.1 手动模拟故障

关闭一个主节点,观察从节点自动晋升:

# 停止一个主节点(比如 redis-cluster-1,它负责 0-5460 槽)
docker stop redis-cluster-1

# 稍等片刻(默认超时 5 秒),查看集群状态
docker exec -it redis-cluster-2 redis-cli cluster nodes

可以看到原本 redis-cluster-1 的从节点(如 172.23.0.2:6379)已经晋升为 master

8.2 故障转移原理

  1. 主节点宕机后,从节点检测到 PING 超时
  2. 从节点等待集群超时时间cluster-node-timeout,默认 5 秒)
  3. 通过 Raft 类似算法 发起选举
  4. 获得多数主节点投票后晋升为新主节点
  5. 接管原主节点的所有槽位

8.3 恢复原主节点

# 重启之前关闭的节点
docker start redis-cluster-1

# 它会以从节点身份重新加入集群,成为新主节点的副本

九、Hash Tag:将关联 Key 存入同一槽位

9.1 为什么需要 Hash Tag?

Redis Cluster 的事务MULTI/EXEC)和 Lua 脚本要求所有操作的 Key 必须在同一个哈希槽,否则会报错:

(error) CROSSSLOT Keys in request don't hash to the same slot

同时,批量操作MGET/MSET)跨节点也有额外的网络开销。

9.2 Hash Tag 语法

Key 中 {...} 部分只使用花括号内的内容计算哈希槽:

# 这两个 Key 都会用 "user:1" 计算哈希槽
{user:1}:name
{user:1}:age

9.3 验证

# 查看两个 Key 的哈希槽(注意:不加 -c 不会自动重定向但 KEYSLOT 命令不受影响)
docker exec -it redis-cluster-1 redis-cli CLUSTER KEYSLOT {user:1}:name
docker exec -it redis-cluster-1 redis-cli CLUSTER KEYSLOT {user:1}:age

两个命令返回相同的槽位号。

9.4 实际使用

# 写入关联 Key
docker exec -it redis-cluster-1 redis-cli -c set {user:1}:name Alice
docker exec -it redis-cluster-1 redis-cli -c set {user:1}:age 30

# 批量读取(在同一槽位,效率更高)
docker exec -it redis-cluster-1 redis-cli -c mget {user:1}:name {user:1}:age

9.5 使用场景

场景 说明
事务操作 MULTI/EXEC 需要所有 Key 在同一槽位
Lua 脚本 EVAL 脚本中所有 Key 必须在同一槽位
批量读写 MGET/MSET 跨槽位需要多次网络请求
关联数据 用户信息、订单明细等高关联度数据

十、总结与最佳实践

关键要点

  1. Cluster 适用场景:数据量大、写入吞吐高、需要自动故障转移的大规模 Redis 部署
  2. 最小生产配置:6 节点(3 主 3 从),每个主节点至少一个副本
  3. 客户端必须支持 Cluster:使用 -c 模式或集成 Cluster 客户端库
  4. 事务限制:MULTI/EXEC 和 Lua 脚本需要配合 Hash Tag {...}
  5. 节点数建议:主节点数量通常为奇数(方便选举投票),建议 3、5、7 个

注意事项

  • 网络延迟:跨节点操作比单节点慢,合理设计 Key 分布
  • 批量操作:尽量用 Hash Tag 让关联 Key 落在同一槽位
  • 扩缩容:生产环境扩缩容时关注槽位迁移对性能的影响
  • 监控:重点关注 cluster_statecluster_slots_okcluster_known_nodes 等指标
posted @ 2026-08-08 21:58  PC2005-cloud  阅读(23)  评论(0)    收藏  举报