Database · #sharding#redis#cluster#high-availability

Redis Cluster集群架构与运维实战

2024.12.25 8 min 3.2k
// 目录 · contents

引言

随着业务数据量和并发量的增长,单机Redis在内存容量和吞吐量上都会遇到瓶颈。Redis Cluster是Redis官方提供的分布式解决方案,它通过数据分片将数据分布到多个节点上,同时提供高可用和自动故障转移能力。本文将深入解析Redis Cluster的架构设计和运维实践。

Redis Cluster 架构概览

graph TD
    subgraph "Redis Cluster (6节点, 3主3从)"
        M1["Master 1<br/>Slots: 0-5460"]
        M2["Master 2<br/>Slots: 5461-10922"]
        M3["Master 3<br/>Slots: 10923-16383"]
        S1["Slave 1<br/>(Master 2的副本)"]
        S2["Slave 2<br/>(Master 3的副本)"]
        S3["Slave 3<br/>(Master 1的副本)"]

        M1 <-->|Gossip| M2
        M2 <-->|Gossip| M3
        M1 <-->|Gossip| M3

        M1 -.->|复制| S3
        M2 -.->|复制| S1
        M3 -.->|复制| S2
    end

    C[Client] --> M1
    C --> M2
    C --> M3

Redis Cluster的核心特性: - 数据分片:16384个Hash Slot分布在多个Master节点上 - 高可用:每个Master有一个或多个Slave,Master故障时自动failover - 去中心化:无代理层,客户端直连节点,节点间通过Gossip协议通信 - 线性扩展:可以在线添加/移除节点,数据自动迁移

Hash Slot 分片机制

Slot 计算

Redis Cluster使用CRC16算法将key映射到16384个slot中的一个:

1
SLOT = CRC16(key) % 16384
1
2
3
4
5
6
# 查看key属于哪个slot
redis-cli CLUSTER KEYSLOT mykey
# (integer) 14687

redis-cli CLUSTER KEYSLOT user:1001
# (integer) 5649

Hash Tag

Hash Tag允许将相关的key映射到同一个slot,确保可以在同一个节点上执行多key操作:

1
2
3
4
5
6
7
8
# 花括号内的部分用于计算slot
# 以下三个key都会根据 "user:1001" 计算slot
SET {user:1001}:name "Alice"
SET {user:1001}:age "25"
SET {user:1001}:email "[email protected]"

# 现在可以在同一节点上执行MGET
MGET {user:1001}:name {user:1001}:age {user:1001}:email

为什么是 16384 个 Slot

Redis作者antirez解释了选择16384而非65536的原因:

  1. Gossip消息大小:每个节点发送的心跳包需要携带slot信息的bitmap,16384个slot只需2KB,而65536个slot需要8KB
  2. 集群规模:Redis Cluster建议不超过1000个节点,16384个slot完全够用
  3. 压缩率:16384个slot的bitmap在传输时压缩率更高

Gossip 协议与节点通信

通信机制

Redis Cluster节点间使用Gossip协议进行去中心化的信息交换,每个节点维护着集群的完整拓扑信息。

sequenceDiagram
    participant N1 as Node 1
    participant N2 as Node 2
    participant N3 as Node 3

    Note over N1, N3: 每秒随机选择节点发送PING

    N1->>N2: PING (携带自身信息 + 随机选择的其他节点信息)
    N2->>N1: PONG (携带自身信息 + 随机选择的其他节点信息)

    Note over N1: 更新N2的状态信息
    Note over N2: 更新N1的状态信息

    N2->>N3: PING
    N3->>N2: PONG

    Note over N1, N3: 信息逐渐传播到所有节点

消息类型

消息 用途
PING 探测对方是否在线,携带集群状态信息
PONG 回复PING,同样携带集群状态信息
MEET 邀请新节点加入集群
FAIL 广播某节点已确认下线
PUBLISH 向集群中所有节点广播消息
UPDATE 通知其他节点更新slot-node映射

PFAIL 与 FAIL 的区别

flowchart TD
    A[Node A 向 Node B 发送PING] --> B{Node B 响应?}
    B -->|是| C[正常]
    B -->|否,超过 cluster-node-timeout| D[Node A 标记 Node B 为 PFAIL<br/>主观下线]
    D --> E[Node A 通过 Gossip 传播 PFAIL 信息]
    E --> F{集群中过半Master<br/>标记 Node B 为 PFAIL?}
    F -->|是| G[Node B 被标记为 FAIL<br/>客观下线]
    F -->|否| H[继续等待更多节点确认]
    G --> I[广播 FAIL 消息到所有节点]
    I --> J[触发故障转移流程]

故障转移(Failover)

当Master节点被标记为FAIL后,其Slave节点会自动发起故障转移。

故障转移流程

sequenceDiagram
    participant M as Master (故障)
    participant S1 as Slave 1 (数据最新)
    participant S2 as Slave 2
    participant Others as 其他Master节点

    Note over M: Master宕机

    Others->>Others: 检测到Master FAIL

    Note over S1, S2: Slave发起选举

    S1->>Others: FAILOVER_AUTH_REQUEST (请求投票)
    S2->>Others: FAILOVER_AUTH_REQUEST (请求投票)

    Note over Others: 每个Master只能投一票<br/>优先投给数据最新的Slave

    Others->>S1: FAILOVER_AUTH_ACK (投票给S1)

    Note over S1: 获得过半Master投票

    S1->>S1: 1. 执行 SLAVEOF NO ONE
    S1->>S1: 2. 接管Master的所有Slot
    S1->>Others: 3. 广播新的配置纪元
    S1->>S2: 4. S2成为新Master(S1)的Slave

    Note over S1: 故障转移完成,S1成为新Master

Slave 选举优先级

  1. 复制偏移量:数据最新(replication offset最大)的Slave优先
  2. runid字典序:偏移量相同时,runid最小的Slave优先
  3. slave-priority:配置值越小优先级越高(0表示不参与选举)
1
2
3
# 配置slave优先级
replica-priority 100 # 默认值
# 设为0表示此Slave永远不会被选为Master

MOVED 与 ASK 重定向

MOVED 重定向

当客户端请求的key不在当前节点负责的slot时,返回MOVED重定向:

1
2
3
4
5
# 客户端连接到Node1,但key属于Node2负责的slot
redis-cli -c -h node1 -p 7000
> SET user:5000 "Alice"
-> Redirected to slot [4092] located at 192.168.1.102:7001
OK
sequenceDiagram
    participant C as Client
    participant N1 as Node 1
    participant N2 as Node 2

    C->>N1: SET user:5000 "Alice"
    N1->>C: MOVED 4092 192.168.1.102:7001
    Note over C: 更新本地slot映射缓存
    C->>N2: SET user:5000 "Alice"
    N2->>C: OK

ASK 重定向

ASK重定向发生在slot迁移过程中,表示key可能正在迁移到目标节点:

sequenceDiagram
    participant C as Client
    participant Src as Source Node
    participant Dst as Destination Node

    Note over Src, Dst: Slot迁移进行中

    C->>Src: GET key1
    Note over Src: key1已迁移到Dst
    Src->>C: ASK 4092 192.168.1.102:7001

    Note over C: ASK是一次性重定向,不更新缓存

    C->>Dst: ASKING
    Dst->>C: OK
    C->>Dst: GET key1
    Dst->>C: "value1"

MOVED vs ASK 的区别: - MOVED:slot已永久移动到新节点,客户端应更新缓存 - ASK:slot正在迁移中,仅本次请求重定向到目标节点,不更新缓存

集群搭建与管理

快速搭建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 创建6个Redis实例的配置(3主3从)
# redis-7000.conf 示例
port 7000
cluster-enabled yes
cluster-config-file nodes-7000.conf
cluster-node-timeout 15000
appendonly yes
dir /data/redis/7000

# 启动6个实例
redis-server redis-7000.conf
redis-server redis-7001.conf
redis-server redis-7002.conf
redis-server redis-7003.conf
redis-server redis-7004.conf
redis-server redis-7005.conf

# 创建集群(3主3从)
redis-cli --cluster create \
192.168.1.101:7000 \
192.168.1.101:7001 \
192.168.1.102:7002 \
192.168.1.102:7003 \
192.168.1.103:7004 \
192.168.1.103:7005 \
--cluster-replicas 1

常用集群命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查看集群信息
redis-cli -c -p 7000 CLUSTER INFO

# 查看节点列表
redis-cli -c -p 7000 CLUSTER NODES

# 查看slot分配
redis-cli -c -p 7000 CLUSTER SLOTS

# 检查集群健康状态
redis-cli --cluster check 192.168.1.101:7000

# 查看集群信息摘要
redis-cli --cluster info 192.168.1.101:7000

在线扩容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 第一步: 添加新的Master节点
redis-cli --cluster add-node \
192.168.1.104:7006 \ # 新节点
192.168.1.101:7000 # 集群中任一已有节点

# 第二步: 为新Master迁移slot
redis-cli --cluster reshard 192.168.1.101:7000
# 交互式输入:
# How many slots do you want to move? 4096
# What is the receiving node ID? <新节点ID>
# Source node: all (从所有现有Master均匀迁移)

# 第三步: 添加新的Slave节点
redis-cli --cluster add-node \
192.168.1.104:7007 \ # 新Slave
192.168.1.101:7000 \ # 集群中任一已有节点
--cluster-slave \
--cluster-master-id <新Master的ID>
flowchart LR
    subgraph 扩容前
        A["M1: 0-5460<br/>(5461 slots)"]
        B["M2: 5461-10922<br/>(5462 slots)"]
        C["M3: 10923-16383<br/>(5461 slots)"]
    end

    subgraph 扩容后
        D["M1: 0-4095<br/>(4096 slots)"]
        E["M2: 5461-9556<br/>(4096 slots)"]
        F["M3: 10923-15018<br/>(4096 slots)"]
        G["M4: 4096-5460<br/>9557-10922<br/>15019-16383<br/>(4096 slots)"]
    end

    A --> D
    B --> E
    C --> F
    A -.->|迁移| G
    B -.->|迁移| G
    C -.->|迁移| G

在线缩容

1
2
3
4
5
6
7
8
9
10
11
12
13
# 第一步: 将待移除节点的slot迁移到其他节点
redis-cli --cluster reshard 192.168.1.101:7000
# 将所有slot从待移除节点迁出

# 第二步: 移除Slave节点
redis-cli --cluster del-node \
192.168.1.101:7000 \ # 集群中任一节点
<Slave节点ID>

# 第三步: 移除Master节点(确保已无slot)
redis-cli --cluster del-node \
192.168.1.101:7000 \
<Master节点ID>

手动故障转移

1
2
3
4
5
6
7
8
# 在Slave上执行(用于计划内的主从切换)
redis-cli -p 7003 CLUSTER FAILOVER

# 强制故障转移(即使Master不可达)
redis-cli -p 7003 CLUSTER FAILOVER FORCE

# 强制接管(不经过选举,直接接管slot)
redis-cli -p 7003 CLUSTER FAILOVER TAKEOVER

关键配置参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 集群超时时间(毫秒),节点无响应超过此时间被标记为PFAIL
cluster-node-timeout 15000

# 当负责的slot有部分不可用时,是否继续服务
# yes: 部分slot不可用时集群仍可用(默认no)
cluster-allow-reads-when-down no

# 当集群有slot未分配时,是否拒绝写入
# yes: 有未分配slot时拒绝所有写入(默认yes)
cluster-require-full-coverage yes

# Slave在与Master断开多长时间后不参与failover选举
# 0表示Slave总是参与选举
cluster-replica-validity-factor 10
# 实际超时 = cluster-node-timeout * cluster-replica-validity-factor

# 保证每个Master至少有N个Slave,否则不允许其Slave参与failover
cluster-allow-replica-migration yes

监控与运维

关键监控指标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 集群状态
redis-cli -p 7000 CLUSTER INFO
# cluster_state:ok -- 集群状态(ok/fail)
# cluster_slots_assigned:16384 -- 已分配slot数
# cluster_slots_ok:16384 -- 正常slot数
# cluster_slots_pfail:0 -- PFAIL状态的slot数
# cluster_slots_fail:0 -- FAIL状态的slot数
# cluster_known_nodes:6 -- 已知节点数
# cluster_size:3 -- Master数量

# 内存使用
redis-cli -p 7000 INFO memory
# used_memory_human:2.50G
# maxmemory_human:4.00G
# mem_fragmentation_ratio:1.15

# 各节点QPS
redis-cli -p 7000 INFO stats
# instantaneous_ops_per_sec:25000

# 慢查询
redis-cli -p 7000 SLOWLOG GET 10

常见问题与解决方案

问题一:数据倾斜(Hot Spot)

1
2
3
4
5
6
7
8
9
# 查看各节点的key分布
redis-cli --cluster info 192.168.1.101:7000

# 解决方案:
# 1. 检查是否有大key
redis-cli --bigkeys -h 192.168.1.101 -p 7000

# 2. 使用Hash Tag时注意分散
# 避免所有数据都映射到同一个slot

问题二:集群脑裂

1
2
3
4
# 网络分区可能导致脑裂
# 预防: 配置 min-replicas-to-write
min-replicas-to-write 1 # Master至少有1个Slave才接受写入
min-replicas-max-lag 10 # Slave延迟不超过10秒

问题三:大Key迁移超时

1
2
3
# 大Key迁移可能导致阻塞
# 预防: 定期检查并拆分大Key
# cluster-migration-barrier 控制最少Slave数量

客户端最佳实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Jedis集群客户端示例
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("192.168.1.101", 7000));
nodes.add(new HostAndPort("192.168.1.101", 7001));
nodes.add(new HostAndPort("192.168.1.102", 7002));

JedisCluster jedisCluster = new JedisCluster(
nodes,
5000, // connectionTimeout
5000, // soTimeout
3, // maxAttempts (MOVED/ASK重试次数)
"password",
new GenericObjectPoolConfig<>()
);

// Pipeline in Cluster: 需要按slot分组
// 使用Hash Tag确保相关key在同一slot
jedisCluster.set("{order:1001}:info", "...");
jedisCluster.set("{order:1001}:items", "...");

总结

Redis Cluster是一个设计精良的分布式系统:

  • Hash Slot分片提供了灵活的数据分布方式,16384个slot在集群节点间按需分配
  • Gossip协议实现了去中心化的节点间通信,自动传播集群拓扑变化
  • 自动故障转移基于PFAIL/FAIL两阶段检测和Slave选举机制,保证了高可用性
  • 在线扩缩容通过slot迁移实现,对业务透明
  • 运维中需要特别关注数据倾斜、网络分区和大Key问题
  • 客户端需要正确处理MOVED和ASK重定向,并使用Hash Tag管理相关key的分布
作者 · authorzt
发布 · date2024-12-25
篇幅 · length3.2k 字 · 8 min
许可 · licenseCC BY-SA 4.0
$ echo "comments" · 评论