← 返回最佳实践列表

*Redis Cluster 分片集群部署与运维实战指南:从原理到生产落地的完整方案

基于 Redis 7.2 版本,深入解析 Cluster 分片架构的核心原理、数据路由机制、故障转移流程,提供从零部署到生产运维的完整命令、代码与实战案例,帮助团队构建支撑百万 QPS 的分布式缓存层。


*目录


*一、为什么需要 Redis Cluster

单机 Redis 的瓶颈在数据量超过 50GB 或 QPS 超过 10 万时开始显现:

  • 内存上限:单节点内存受限于物理服务器,64GB 已是主流服务器的上限
  • CPU 瓶颈:单线程命令执行,单节点 QPS 极限约 10~15 万(简单命令)
  • 持久化压力:RDB 和 AOF 在数据量大时消耗大量 CPU 和磁盘 I/O
  • 单点故障:无自动分片能力,主节点宕机需依赖 Sentinel 手动切换,数据无法自动重平衡

Redis Cluster 提供数据分片(Sharding) + 故障转移(Failover)的原生解决方案,将数据自动分布到 16384 个哈希槽(Slot),每个节点负责一部分 Slot,客户端通过 MOVED/ASK 重定向访问正确节点。

适用版本:Redis 7.0+(生产环境建议 7.2 或更高版本,修复了早期 Cluster 的多个边界故障) 测试环境:Redis 7.2.4 / CentOS 8 / 3 主 3 从 架构


*二、Cluster 核心架构与数据分片原理

*2.1 哈希槽(Slot)机制

Redis Cluster 将键空间划分为 16384 个固定槽位(0~16383),每个主节点负责一段连续的 Slot 范围。键的归属通过 CRC16 算法计算:

Slot = CRC16(key) % 16384

例如键 "user:10001" 的计算过程:

# 使用 Redis 内置的 CLUSTER KEYSLOT 查看
redis-cli -p 6379 CLUSTER KEYSLOT user:10001
# 返回: (integer) 8100

如果节点 A 负责 Slot 0~5460,节点 B 负责 5461~10922,节点 C 负责 10923~16383,则 user:10001(Slot 8100)存储在节点 B。

为什么用 Hash Tag?

Cluster 模式下,多键操作(MGET、MSET、事务)要求所有键处于同一 Slot。Redis 通过 Hash Tag 实现:

键 "user:{10001}:profile" 和 "user:{10001}:orders" 只取 {} 内内容计算 CRC16
即都使用 "10001" 计算,保证落入同一 Slot

*2.2 架构拓扑

                    ┌─────────────┐
                    │  客户端    │
                    │ (Smart)    │
                    └──────┬──────┘
                           │
           ┌───────────────┼───────────────┐
           │               │               │
      ┌────┴────┐     ┌────┴────┐     ┌────┴────┐
      │ Master A │     │ Master B │     │ Master C │
      │ Slots   │     │ Slots   │     │ Slots   │
      │ 0-5460  │     │ 5461-10922│    │ 10923-16383│
      └────┬────┘     └────┬────┘     └────┬────┘
           │               │               │
      ┌────┴────┐     ┌────┴────┐     ┌────┴────┐
      │ Slave A1 │     │ Slave B1 │     │ Slave C1 │
      │ (副本)   │     │ (副本)   │     │ (副本)   │
      └─────────┘     └─────────┘     └─────────┘
  • 3 主节点负责读写,3 从节点负责复制和故障切换
  • 每个主节点至少 1 个从节点,建议 2 个从节点提升容错能力
  • 主从节点之间通过异步复制同步数据(Redis 7.0+ 支持 Share Replication 减少复制带宽)

*2.3 节点间通信:Gossip 协议

Cluster 节点通过 Gossip 协议交换状态信息,每个节点每秒随机向其他节点发送 PING 消息,携带:

  • 自身节点 ID、地址、角色(主/从)
  • 负责的 Slot 范围
  • 已知的其他节点状态(故障标记、配置版本)

Gossip 机制保证集群状态在节点间传播,无需中心节点协调配置。


*三、Cluster 节点通信与故障检测机制

*3.1 故障检测流程

Redis Cluster 的故障检测采用主观下线(PFAIL) + 客观下线(FAIL) 两阶段机制:

阶段 条件 行为
主观下线 节点 A 在 cluster-node-timeout 内未收到节点 B 的 PONG 回复 节点 A 标记 B 为 PFAIL
客观下线 超过半数主节点(含 A)都标记 B 为 PFAIL 节点 B 被标记为 FAIL,触发从节点晋升

从节点晋升流程:

  1. 从节点发现主节点进入 FAIL 状态
  2. 从节点发起选举(Epoch 自增,向其他主节点请求投票)
  3. 获得多数主节点投票后,从节点晋升为主节点
  4. 新主节点接管原主节点的 Slot,广播更新配置

*3.2 关键配置参数

# redis.conf 中的 Cluster 核心配置
cluster-enabled yes                          # 启用 Cluster 模式
cluster-config-file nodes-6379.conf          # 节点状态持久化文件
cluster-node-timeout 15000                   # 故障检测超时(毫秒)
cluster-require-full-coverage no             # 允许部分 Slot 不可用(推荐 no)
cluster-replica-validity-factor 10           # 从节点复制延迟容忍倍数
cluster-migration-barrier 1                  # 最少保留从节点数才允许迁移

参数调优建议

  • cluster-node-timeout:网络延迟高时适当增大(30s),但会增加故障检测时间;低延迟环境可设为 5s
  • cluster-require-full-coverage:生产环境设为 no,避免局部故障导致整个集群拒绝服务
  • cluster-allow-replica-migration:Redis 7.0+ 新增,设为 yes 让从节点自动迁移到缺少副本的主节点

*四、从零部署 6 节点 Cluster 集群

*4.1 环境准备

6 台服务器(或同一台 6 个端口):

节点 IP 端口 角色
node-1 192.168.1.11 6379 Master
node-2 192.168.1.12 6379 Master
node-3 192.168.1.13 6379 Master
node-4 192.168.1.14 6379 Slave (node-1)
node-5 192.168.1.15 6379 Slave (node-2)
node-6 192.168.1.16 6379 Slave (node-3)

*4.2 配置文件模板

# 每台服务器执行:创建配置目录
mkdir -p /etc/redis /var/lib/redis

# 编写 redis.conf
cat > /etc/redis/redis.conf << 'EOF'
port 6379
bind 192.168.1.11 127.0.0.1
daemonize yes
pidfile /var/run/redis_6379.pid
logfile /var/log/redis/redis.log
dir /var/lib/redis

# 持久化配置
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
save 900 1
save 300 10
save 60 10000

# 内存管理
maxmemory 8gb
maxmemory-policy allkeys-lru

# Cluster 配置
cluster-enabled yes
cluster-config-file /var/lib/redis/nodes-6379.conf
cluster-node-timeout 15000
cluster-require-full-coverage no
cluster-allow-replica-migration yes

# 安全配置(生产环境必须启用)
requirepass "YourStrongPassword123"
masterauth "YourStrongPassword123"
aclfile /etc/redis/users.acl
EOF

*4.3 启动所有节点

# 6 台服务器分别执行
redis-server /etc/redis/redis.conf

# 验证启动
redis-cli -h 192.168.1.11 -p 6379 -a "YourStrongPassword123" PING
# 返回 PONG 表示启动成功

*4.4 创建集群

# 在任意一台服务器执行
redis-cli --cluster create \
  192.168.1.11:6379 192.168.1.12:6379 192.168.1.13:6379 \
  192.168.1.14:6379 192.168.1.15:6379 192.168.1.16:6379 \
  --cluster-replicas 1 \
  -a "YourStrongPassword123"

# 输出示例:
# >>> Performing hash slots allocation on 6 nodes...
# Master[0] -> Slots 0 - 5460
# Master[1] -> Slots 5461 - 10922
# Master[2] -> Slots 10923 - 16383
# Adding replica 192.168.1.14:6379 to 192.168.1.11:6379
# Adding replica 192.168.1.15:6379 to 192.168.1.12:6379
# Adding replica 192.168.1.16:6379 to 192.168.1.13:6379

*4.5 验证集群状态

# 查看集群节点信息
redis-cli -h 192.168.1.11 -p 6379 -a "YourStrongPassword123" CLUSTER NODES

# 查看 Slot 分配
redis-cli -h 192.168.1.11 -p 6379 -a "YourStrongPassword123" CLUSTER SLOTS

# 检查集群健康状态
redis-cli --cluster check 192.168.1.11:6379 -a "YourStrongPassword123"

*五、客户端连接与数据路由

*5.1 Redis CLI 连接

# 使用 -c 参数启用集群模式(自动处理 MOVED/ASK 重定向)
redis-cli -h 192.168.1.11 -p 6379 -a "YourStrongPassword123" -c

# 写入数据(自动路由到正确节点)
127.0.0.1:6379> SET user:10001 "Alice"
-> Redirected to slot [8100] located at 192.168.1.12:6379
OK

# 读取数据
127.0.0.1:6379> GET user:10001
-> Redirected to slot [8100] located at 192.168.1.12:6379
"Alice"

*5.2 Python 客户端(redis-py)

from redis.cluster import RedisCluster
from redis import RedisError

# 集群客户端配置
startup_nodes = [
    {"host": "192.168.1.11", "port": 6379},
    {"host": "192.168.1.12", "port": 6379},
    {"host": "192.168.1.13", "port": 6379}
]

try:
    rc = RedisCluster(
        startup_nodes=startup_nodes,
        password="YourStrongPassword123",
        decode_responses=True,
        skip_full_coverage_check=True,  # 对应 cluster-require-full-coverage no
        max_connections_per_node=20,
        socket_connect_timeout=5,
        socket_timeout=5
    )

    # 写入数据(自动计算 Slot 并路由)
    rc.set("user:10001", "Alice")
    rc.set("user:10002", "Bob")

    # 使用 Hash Tag 保证多键操作原子性
    rc.set("order:{10001}:items", "item1,item2")
    rc.set("order:{10001}:total", "199.99")

    # 事务(必须同一 Slot)
    pipe = rc.pipeline(transaction=True)
    pipe.multi()
    pipe.incr("counter:{10001}")
    pipe.get("counter:{10001}")
    results = pipe.execute()
    print(f"Counter value: {results[1]}")

    # 读取数据
    value = rc.get("user:10001")
    print(f"User: {value}")

except RedisError as e:
    print(f"Redis cluster error: {e}")
finally:
    rc.close()

*5.3 Java 客户端(Jedis)

import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisPoolConfig;
import java.util.Set;
import java.util.HashSet;

public class RedisClusterExample {
    public static void main(String[] args) {
        // 配置连接池
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(100);
        poolConfig.setMaxIdle(20);
        poolConfig.setMinIdle(5);

        // 集群节点配置
        Set<HostAndPort> nodes = new HashSet<>();
        nodes.add(new HostAndPort("192.168.1.11", 6379));
        nodes.add(new HostAndPort("192.168.1.12", 6379));
        nodes.add(new HostAndPort("192.168.1.13", 6379));

        // 创建集群客户端
        JedisCluster jedisCluster = new JedisCluster(
            nodes,
            5000,    // connectionTimeout
            5000,    // soTimeout
            3,       // maxAttempts
            "YourStrongPassword123",
            poolConfig
        );

        try {
            // 写入数据
            jedisCluster.set("product:10001", "Redis实战书");
            jedisCluster.set("product:10001:price", "79.00");

            // 使用 Hash Tag 实现事务
            String watchKey = "inventory:{10001}";
            jedisCluster.watch(watchKey);
            String current = jedisCluster.get(watchKey);
            int stock = Integer.parseInt(current != null ? current : "0");

            if (stock > 0) {
                jedisCluster.multi();
                jedisCluster.decr(watchKey);
                jedisCluster.set("order:{10001}:status", "PAID");
                jedisCluster.exec();
                System.out.println("Order placed successfully");
            } else {
                jedisCluster.unwatch();
                System.out.println("Out of stock");
            }

            // 读取数据
            String value = jedisCluster.get("product:10001");
            System.out.println("Product: " + value);

        } catch (Exception e) {
            System.err.println("Redis operation failed: " + e.getMessage());
        } finally {
            jedisCluster.close();
        }
    }
}

*六、生产环境运维实战

*6.1 集群扩缩容

扩容:添加新主节点

# 1. 启动新节点(192.168.1.17:6379)并加入集群
redis-cli --cluster add-node 192.168.1.17:6379 192.168.1.11:6379 \
  -a "YourStrongPassword123"

# 2. 分配 Slot(从现有节点迁移)
redis-cli --cluster reshard 192.168.1.11:6379 \
  --cluster-from all \
  --cluster-to <new-node-id> \
  --cluster-slots 4096 \
  --cluster-yes \
  -a "YourStrongPassword123"

# 3. 为新主节点添加从节点
redis-cli --cluster add-node 192.168.1.18:6379 192.168.1.17:6379 \
  --cluster-slave \
  --cluster-master-id <new-node-id> \
  -a "YourStrongPassword123"

缩容:移除节点

# 1. 先迁移该节点的 Slot 到其他节点
redis-cli --cluster reshard 192.168.1.11:6379 \
  --cluster-from <remove-node-id> \
  --cluster-to <target-node-id> \
  --cluster-slots 4096 \
  --cluster-yes \
  -a "YourStrongPassword123"

# 2. 移除节点
redis-cli --cluster del-node 192.168.1.11:6379 <remove-node-id> \
  -a "YourStrongPassword123"

*6.2 数据迁移与重新平衡

# 使用 rebalance 自动平衡各节点 Slot 数量
redis-cli --cluster rebalance 192.168.1.11:6379 \
  --cluster-use-empty-masters \
  --cluster-simulate \
  -a "YourStrongPassword123"

# 查看迁移进度
redis-cli -h 192.168.1.11 -p 6379 -a "YourStrongPassword123" INFO keyspace

*6.3 备份与恢复

# 1. 对每个主节点执行 BGSAVE
redis-cli -h 192.168.1.11 -p 6379 -a "YourStrongPassword123" BGSAVE

# 2. 复制 RDB 文件到备份目录
for host in 192.168.1.11 192.168.1.12 192.168.1.13; do
  scp root@${host}:/var/lib/redis/dump.rdb /backup/redis/$(date +%Y%m%d)/${host}/
done

# 3. 恢复:停止节点,替换 RDB 文件,重新启动(Cluster 配置会保留)

*6.4 监控关键指标

# 集群状态监控
redis-cli -h 192.168.1.11 -p 6379 -a "YourStrongPassword123" CLUSTER INFO

# 关键指标解读
# cluster_state:ok              # 集群状态正常
# cluster_slots_assigned:16384  # 所有 Slot 已分配
# cluster_slots_ok:16384        # 所有 Slot 正常
# cluster_slots_fail:0          # 无故障 Slot
# cluster_known_nodes:6         # 已知节点数
# cluster_size:3                # 主节点数
# cluster_current_epoch:15      # 配置纪元(故障转移后递增)

# 节点级监控
redis-cli -h 192.168.1.11 -p 6379 -a "YourStrongPassword123" INFO replication
# master_link_status:up         # 主从复制正常
# master_last_io_seconds_ago:1  # 上次复制延迟

*七、实战案例:电商大促期间的 Cluster 扩容与故障恢复

*7.1 背景

某电商平台使用 3 主 3 从 Cluster 支撑商品缓存,日常 QPS 约 5 万。大促前预估 QPS 将飙升至 25 万,现有集群 CPU 使用率已达 60%,内存使用率 70%,需要扩容。

*7.2 扩容方案

目标:3 主 → 6 主,每主带 1 从,共 12 节点

执行步骤

# 1. 预热新节点(提前 1 天完成)
for ip in 192.168.1.17 192.168.1.18 192.168.1.19 192.168.1.20 192.168.1.21 192.168.1.22; do
  redis-cli --cluster add-node ${ip}:6379 192.168.1.11:6379 -a "YourStrongPassword123"
done

# 2. 逐个迁移 Slot(每次迁移 1365 个 Slot,避免影响业务)
# 从 node-1 迁移到新 node-4
redis-cli --cluster reshard 192.168.1.11:6379 \
  --cluster-from <node-1-id> \
  --cluster-to <node-4-id> \
  --cluster-slots 1365 \
  --cluster-yes \
  --cluster-timeout 30000 \
  --cluster-pipeline 100 \
  -a "YourStrongPassword123"

# 3. 观察延迟和复制状态,确认无异常后继续迁移下一批
redis-cli -h 192.168.1.11 -p 6379 -a "YourStrongPassword123" INFO replication

*7.3 故障模拟:主节点宕机

大促期间,node-2(Master,负责 Slot 5461~10922)因硬件故障宕机。

自动故障转移过程

# 1. 查看节点状态(从 node-1 执行)
redis-cli -h 192.168.1.11 -p 6379 -a "YourStrongPassword123" CLUSTER NODES

# 输出:
# <node-2-id> 192.168.1.12:6379 master - fail? - 16383 16383 1 disconnected
# <node-5-id> 192.168.1.15:6379 master - 0 0 7 connected 5461-10922  # 已晋升

# 2. 确认集群状态
redis-cli -h 192.168.1.11 -p 6379 -a "YourStrongPassword123" CLUSTER INFO
# cluster_state:ok
# cluster_slots_fail:0

故障转移耗时:约 15~30 秒(cluster-node-timeout=15000

恢复故障节点

# 1. 修复服务器后启动 Redis
redis-server /etc/redis/redis.conf

# 2. 将原主节点作为从节点加入
redis-cli --cluster add-node 192.168.1.12:6379 192.168.1.11:6379 \
  --cluster-slave \
  --cluster-master-id <node-5-id> \
  -a "YourStrongPassword123"

# 3. 可选:重新切换主从关系(需手动执行 CLUSTER FAILOVER)
redis-cli -h 192.168.1.12 -p 6379 -a "YourStrongPassword123" CLUSTER FAILOVER

*7.4 效果对比

指标 扩容前 扩容后 提升
总节点数 6 12 2x
理论 QPS 上限 15 万 30 万 2x
单节点内存上限 8GB 8GB -
总内存容量 24GB 48GB 2x
故障转移影响范围 1/3 Slot 1/6 Slot 降低 50%

*八、故障排查与常见问题诊断

*8.1 常见问题 1:MOVED 重定向循环

现象:客户端收到 MOVED 8100 192.168.1.12:6379,重定向后再次收到相同 MOVED。

根因

  • 客户端未启用 Cluster 模式(如 redis-cli 未加 -c
  • 集群正在 Reshard,Slot 归属已变更但客户端缓存旧拓扑

诊断

# 检查 Slot 实际归属
redis-cli -h 192.168.1.12 -p 6379 -a "YourStrongPassword123" CLUSTER KEYSLOT user:10001
redis-cli -h 192.168.1.12 -p 6379 -a "YourStrongPassword123" CLUSTER NODES | grep "8100"

解决方案

  • 客户端使用 Cluster 客户端库(自动处理 MOVED)
  • 客户端定期刷新节点拓扑(redis-py 默认每 60 秒刷新)

*8.2 常见问题 2:集群进入 FAIL 状态

现象CLUSTER INFO 显示 cluster_state:fail

根因排查

# 1. 检查未分配的 Slot
redis-cli -h 192.168.1.11 -p 6379 -a "YourStrongPassword123" CLUSTER SLOTS | grep -v "\["

# 2. 检查节点通信
redis-cli -h 192.168.1.11 -p 6379 -a "YourStrongPassword123" CLUSTER NODES
# 关注 flags: disconnected, fail?, fail

# 3. 修复:为未分配 Slot 指定节点
redis-cli --cluster fix 192.168.1.11:6379 -a "YourStrongPassword123"

*8.3 常见问题 3:复制延迟导致从节点晋升数据丢失

现象:主节点宕机后,从节点晋升为主节点,但数据比旧主节点少。

根因

  • 异步复制存在延迟窗口
  • cluster-node-timeout 设置过短,从节点复制未追上就被迫晋升

解决方案

# 增大复制延迟容忍度
redis-cli -h 192.168.1.11 -p 6379 -a "YourStrongPassword123" CONFIG SET cluster-replica-validity-factor 20

# 监控复制延迟(从节点执行)
redis-cli -h 192.168.1.14 -p 6379 -a "YourStrongPassword123" INFO replication
# master_last_io_seconds_ago: 5  # 应小于 timeout 的 10%

*九、FAQ

Q1:Redis Cluster 最多支持多少个节点?

Redis Cluster 没有硬性节点上限,但 Gossip 协议的通信开销随节点数增加。生产环境建议:

  • 主节点不超过 100 个(Redis 官方推荐)
  • 每主节点配置 1~2 个从节点
  • 总节点数超过 50 时,建议按业务拆分多个独立集群

Q2:Cluster 模式下能否使用 SELECT 切换数据库?

不能。Redis Cluster 仅支持数据库 0,执行 SELECT 1 会返回错误:(error) ERR SELECT is not allowed in cluster mode。如需隔离,建议:

  • 使用键前缀区分(如 user:, order:
  • 按业务拆分独立集群

Q3:为什么 MOVED 和 ASK 两种重定向?

  • MOVED:Slot 已永久迁移到新节点,客户端应更新本地缓存
  • ASK:Slot 正在迁移中(临时状态),客户端只应临时访问目标节点,不应更新缓存

两者的区别是配置纪元(Epoch)和持久性:MOVED 代表拓扑已变更,ASK 代表迁移中的临时访问。

Q4:Cluster 模式下如何实现原子性事务?

Cluster 支持事务的前提是所有涉及的键必须在同一 Slot。方法:

  • 使用 Hash Tag:{user:10001}:balance{user:10001}:log
  • 使用 Lua 脚本(脚本内所有键也需同一 Slot)
  • 跨 Slot 事务需应用层实现两阶段提交或 Saga 模式

Q5:跨机房部署 Cluster 的注意事项?

跨机房部署需保证:

  • 节点间 RTT < 50ms,否则 Gossip 和复制延迟会导致频繁故障切换
  • 仲裁节点(主节点)多数在同一机房,避免脑裂
  • 建议每个机房部署完整副本集,客户端就近读取,写入走主节点机房

*十、总结

Redis Cluster 通过 16384 个哈希槽实现数据分片,结合 Gossip 协议的节点通信和自动故障转移机制,提供了原生水平扩展能力。核心要点回顾:

  1. 部署层面:6 节点起步(3 主 3 从),cluster-require-full-coverage 设为 no,保证局部故障不影响整体可用性
  2. 客户端层面:使用 Smart Client(redis-py、JedisCluster),自动处理 MOVED/ASK 重定向,Hash Tag 保障多键操作原子性
  3. 运维层面:扩容采用渐进式 Reshard,监控 cluster_statemaster_last_io_seconds_ago 等核心指标
  4. 故障恢复:主节点宕机 15~30 秒内自动完成切换,修复后通过 CLUSTER FAILOVER 恢复原有主从结构

对于超过 100 个节点或跨多数据中心的场景,建议评估 Redis Cluster Proxy(如 predixy、corvus)或 Redis Enterprise 的 Active-Active 方案,以降低客户端复杂度并提升跨地域性能。


相关阅读