强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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

🔗 扩展阅读