Redis Cluster 集群模式

一、什么是 Redis 集群

**集群(Cluster)**是一组相互独立的、通过高速网络互联的计算机,它们构成一个组,以单一系统的模式对外提供服务。

二、集群 vs 哨兵:核心区别

这是最容易混淆的两个概念,先理清定位:

维度 Sentinel 哨兵 Cluster 集群
解决什么问题 高可用 —— 主库宕机自动故障转移 数据分片 + 高可用 —— 超大数据集水平扩展
数据分布 所有节点保存全量数据 数据打散到各节点,每个节点只存子集
容量上限 受限于单机内存 理论上无限水平扩展
最小部署 1 主 + N 从 + M 哨兵 至少 6 个节点(3 主 3 从)
中心节点 有(Master 角色) 无中心结构,所有节点地位平等
通信协议 哨兵 → 主从:PING/PUBLISH 节点之间:Gossip 协议

redis_cluster.png

简单记忆

  • 只要数据量单机能装下,只需要高可用 → 用 Sentinel
  • 数据量太大单机装不下 → 必须用 Cluster

三、Redis Cluster 核心特性

3.1 架构概述

  • 版本要求:Redis 3.0 开始引入 Cluster 模式
  • 拓扑结构无中心化(decentralized),没有专门的代理或路由节点
  • 节点关系:每个节点都和其他所有节点保持 TCP 连接
  • 官方推荐:至少 6 个节点才能保证高可用——即 3 主 3 从
  • 元数据同步:节点间通过 Gossip 协议交换状态信息
  • 数据分布:整个数据集按 16384 个槽位(slot) 分散到各主节点

3.2 槽位分配机制(Hash Slot)

这是 Redis Cluster 最核心的设计。

为什么是 16384 个槽?

问题 答案
槽位数量 16384(即 $2^{14}$)
为什么不是更多? 槽位信息通过 Gossip 协议传播,太多会浪费带宽。16384 占用仅 2KB(16 bit × 16384 / 8),心跳包开销极小
为什么不是更少? 太少会导致数据分布不够均匀,迁移时粒度过粗影响性能

分配规则

  1. 整个键空间被划分为 0 ~ 16383 共 16384 个槽位
  2. 每个主节点负责其中一部分槽位的范围(如 Node A 负责 05460, B 负责 546110922, C 负责 10923~16383)
  3. 当客户端操作某个 key 时,先用 CRC16(key) % 16364 计算该 key 属于哪个槽位
  4. 再根据槽位到节点的映射表,找到对应的主节点进行读写
  5. 如果请求到了不负责该槽位的节点,该节点会返回 MOVED 重定向错误,引导客户端访问正确的节点

槽位迁移

Cluster 支持在线的槽位重新分配(resharding):

1
2
3
4
CLUSTER SETSLOT <slot> IMPORTING <node-id>   # 目标节点准备导入
CLUSTER SETSLOT <slot> MIGRATING <node-id> # 源节点准备迁出
CLUSTER GETKEYSINSLOT <slot> <count> # 获取待迁移的 key 列表
MIGRATE target_host port key db timeout # 逐个迁移 key

3.3 Gossip 协议

节点之间通过 Gossip 协议交换以下元数据:

  • 节点状态(online / fail / pfail)
  • 槽位分配信息
  • 其他节点的已知状态

工作方式:每秒随机选择几个节点发送 ping/pong 消息,逐步将信息扩散到全网。虽然消息有延迟,但保证了最终一致性。

四、高可用保障

故障检测与切换流程

关键要点:

  1. 主观下线(PFAIL):某个节点发现目标节点超时未响应(cluster-node-timeout 配置)
  2. 客观下线(FAIL): 超半数主节点确认该节点下线
  3. 选举:从节点发起投票,获得多数票者成为新主节点
  4. 通知更新:新主节点广播自身角色变更,其他节点更新路由表

客户端重定向

当客户端访问的槽位不在当前节点时:

错误类型 含义 处理方式
MOVED 槽位已永久迁移到其他节点 更新本地槽位缓存,后续直连正确节点
ASK 槽位正在迁移中,部分 key 还在原节点 临时去原节点查询一次

大多数 Redis 客户端驱动(Jedis、Lettuce、Redisson 等)都已内置 Cluster 支持,自动处理重定向。

五、实战:搭建一个 3 主 3 从集群

5.1 创建实例目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 在不同端口创建 6 个 Redis 实例(7000~7005)
for port in 7000 7001 7002 7003 7004 7005; do
mkdir -p /opt/redis-cluster/node-${port}
cat > /opt/redis-cluster/node-${port}/redis.conf <<EOF
port ${port}
cluster-enabled yes
cluster-config-file nodes-${port}.conf
cluster-node-timeout 5000
appendonly yes
daemonize yes
pidfile /var/run/redis-${port}.pid
logfile /opt/redis-cluster/node-${port}/debug.log
dir /opt/redis-cluster/node-${port}
EOF
done

5.2 启动所有实例

1
2
3
for port in 7000 7001 7002 7003 7004 7005; do
redis-server /opt/redis-cluster/node-${port}/redis.conf
done

5.3 创建集群

1
2
3
4
5
# --cluster-replica 1 表示每个主节点自动分配 1 个从节点
redis-cli --cluster create \
127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 \
127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \
--cluster-replica 1

执行后系统会自动:

  1. 将 16384 个槽位平均分配给 3 个主节点(7000/7001/7002)
  2. 为每个主节点分配一个从节点(7003→7000, 7004→7001, 7005→7002)

5.4 验证集群状态

1
2
3
4
5
6
7
8
9
# 查看集群信息
redis-cli -p 7000 cluster info

# 查看节点列表
redis-cli -p 7000 cluster nodes

# 测试写入和读取
redis-cli -c -p 7000 set name "test"
redis-cli -c -p 7000 get name

注意:使用 -c 参数启用集群模式,客户端会自动处理 MOVED 重定向。

六、常用命令速查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# === 集群管理 ===
cluster info # 查看集群整体状态
cluster nodes # 查看所有节点及其角色/槽位
cluster slots # 查看槽位分配详情
cluster forget <node-id> # 移除指定节点
cluster meet <ip> <port> # 手动加入新节点

# === 槽位管理 ===
cluster addslots <slot> [slot...] # 手动分配槽位
cluster delslots <slot> [slot...] # 移除槽位
cluster setslot <slot> node <node-id># 设置槽位所属节点
cluster countkeysinslot <slot> # 统计某槽位中的 key 数量
cluster getkeysinslot <slot> <count> # 获取槽位中的 key 列表

# === 故障处理 ===
cluster failover # 手动触发故障转移(在从节点上执行)
cluster reset [HARD|SOFT] # 重置节点集群状态

七、注意事项

  1. 不支持多 key 操作跨槽MGETMSETSUNION 等多 key 命令要求所有 key 必须落在同一个槽位。可以使用 Hash Tag {...} 强制相关 key 落在同一槽位(如 user:{1001}:profileuser:{1001}:orders 会落到同一个槽)
  2. 事务限制MULTI/EXEC 事务中的命令必须作用于同一槽位
  3. 数据库限制:集群模式下只能使用 db 0
  4. Pipeline 优化:客户端应尽量使用 Pipeline 批量操作减少网络往返
  5. 扩容建议:提前规划槽位分配,尽量避免频繁 resharing;生产环境建议使用运维工具(如 Redis Insight、Codis)辅助管理