Redis Cluster 的架构设计及数据在线迁移

深入解析Redis集群的工作原理、节点通信机制及数据迁移策略

分布式系统 Redis 集群架构

Redis Cluster 概述

Redis Cluster是Redis的分布式实现,通过分片(Sharding)将数据分散到多个节点上,实现水平扩展。 它不仅提供了数据分片能力,还支持高可用性和在线数据迁移,使得集群可以在不停机的情况下进行扩容和缩容。

分片机制

Redis Cluster将键空间分为16384个哈希槽(slot),每个键通过CRC16算法计算后映射到不同的槽。集群中的每个节点负责一部分槽的数据存储。

去中心化架构

Redis Cluster采用去中心化架构,所有节点互联并使用Gossip协议进行信息同步,无需依赖中心协调节点。

在线迁移

支持在不停机的情况下进行数据迁移,通过特殊的重定向机制确保迁移过程中的请求正确处理。

分片机制

Redis Cluster将key空间分为16384个slot,每个key通过crc16算法计算后映射到不同的slot。node是cluster中的节点或者分片,每个node负责一段slot区间。

graph LR Client[客户端] -->|"CRC16(key) mod 16384"| Slots[哈希槽空间] Slots -->|0-5460| Node1[节点1] Slots -->|5461-13652| Node2[节点2] Slots -->|13653-16383| Node3[节点3] style Client fill:#f9f9f9,stroke:#333,stroke-width:2px style Slots fill:#e1f5fe,stroke:#333,stroke-width:2px style Node1 fill:#e8f5e9,stroke:#333,stroke-width:2px style Node2 fill:#e8f5e9,stroke:#333,stroke-width:2px style Node3 fill:#e8f5e9,stroke:#333,stroke-width:2px
0 5460 13652 16383

节点属性

每个节点本地都缓存了集群中其他节点的NODE信息,包括了以下信息:

  • node id - 全局唯一的节点标识符
  • IP PORT - 节点的网络地址和端口
  • 节点状态 - 当前节点、主节点、副本节点、失败的节点
  • Master - 如果是副本节点,这里是主节点的id;如果是主节点,这里是 -
  • ping-sent - 当前已发出但未收到回复的PING的开始时间戳(毫秒)
  • pong-recv - 最近一次收到PONG的时间戳(毫秒)
  • config-epoch - 当前节点的配置版本号
  • link-state - 与其他节点进行的连接的状态
  • slot - 该节点负责的slot,可以是slot范围,也可以是单个slot

节点信息示例

1850b1a8fb62ff9d2aaff026e47a2d6dab64f4e0 10.183.8.201:6379@16379 master - 0 1733457041570 5 connected 0-5460 13653-16383
d9c80e7b7e30a41ef24978e2a879204f4a93fe8d 10.183.10.214:6379@16379 slave 525efc1a947590ccc1b924a344ec487d50fa2177 0 1733457041569 4 connected
af04998e85bed3754ab2b36e09a6a70e75e0dd85 10.183.9.249:6379@16379 slave 1850b1a8fb62ff9d2aaff026e47a2d6dab64f4e0 0 1733457041065 5 connected
525efc1a947590ccc1b924a344ec487d50fa2177 10.183.9.254:6379@16379 myself,master - 0 1733457041000 4 connected 5461-13652

集群通信

每个节点除了数据端口之外,还会监听一个管理端口,用来接收节点之间的通信,一般是数据端口加10000(16379)

graph TB subgraph "节点间通信" A[节点A
6379/16379] ---|TCP连接| B[节点B
6379/16379] B ---|TCP连接| C[节点C
6379/16379] C ---|TCP连接| A A -.->|PING| B B -.->|PONG| A B -.->|PING| C C -.->|PONG| B C -.->|PING| A A -.->|PONG| C end style A fill:#f3e5f5,stroke:#333,stroke-width:2px style B fill:#e3f2fd,stroke:#333,stroke-width:2px style C fill:#e8f5e9,stroke:#333,stroke-width:2px

集群拓扑

每个节点都与集群中的其他节点使用TCP进行互联,一条连向对方的连接、一条对方连过来的,所以每个节点会有2×(N-1)条连接。

节点探活

节点之间都在TCP连接上进行PING PONG来探测对方,在把对方标记为不可达之前,会尝试建立新连接进行探活。节点之间使用gossip协议进行信息同步。

节点互信

cluster默认其他节点为未识别的节点,需要使用MEET命令将本节点加入集群中(CLUSTER MEET target-ip target-port)

graph LR A[节点A] --- B[节点B] B --- C[节点C] A --- C D[新节点D] -.->|CLUSTER MEET A-ip A-port| A A -.->|MEET消息| D A -.->|Gossip协议| B & C style A fill:#e3f2fd,stroke:#333,stroke-width:2px style B fill:#e3f2fd,stroke:#333,stroke-width:2px style C fill:#e3f2fd,stroke:#333,stroke-width:2px style D fill:#f3e5f5,stroke:#333,stroke-width:2px,stroke-dasharray: 5 5

节点加入示例

例如当新增一个节点D时,可以对节点D使用命令 CLUSTER MEET A-ip A-port。这时D节点将会和A节点建立连接,并发送MEET消息,A节点将D节点标记为合法节点,并使用gossip协议向B、C节点进行信息同步。

命令重定向

MOVED重定向

client可以向cluster中的任意节点发送命令,当节点发现key不在当前节点,会返回一个moved错误,并附带正确的节点信息,client按照返回的节点信息进行重试。

client可以在收到moved错误后,向任意节点请求最新的节点拓扑信息。

ASK重定向

在SLOT迁移中,源节点发现key不在当前NODE时,会返回ask和目标节点信息,通知client去目标节点访问。

ask是迁移过程中的临时重定向,收到ask后,client只是当次请求向新NODE发送,且不更新本地<SLOT,NODE>对应关系表,后续的命令仍然向源节点发送。

sequenceDiagram participant Client participant NodeA as 源节点A participant NodeB as 目标节点B Client->>NodeA: GET key1 Note over NodeA: key1不在当前节点 NodeA-->>Client: ASK重定向到NodeB Client->>NodeB: ASKING
GET key1 NodeB-->>Client: value1 Client->>NodeA: GET key2 Note over NodeA: key2在当前节点 NodeA-->>Client: value2 Note over Client,NodeB: SLOT迁移完成后 Client->>NodeA: GET key3 NodeA-->>Client: MOVED重定向到NodeB Note over Client: 更新本地拓扑信息 Client->>NodeB: GET key3 NodeB-->>Client: value3

为什么需要ASK重定向?

moved同样有重定向的作用,为什么新增一种ask重定向?因为ask是迁移过程中的临时重定向,收到ask后,client只是当次请求向新NODE发送,且不更新本地<SLOT,NODE>对应关系表,后续的命令仍然向源节点发送。SLOT迁移完成后,才会下发moved,client会更新本地<SLOT,NODE>对应关系表,后续的命令会发送给新NODE。

SLOT在线迁移

在线迁移slot的步骤:要迁入的节点称为"目标节点",迁出的节点称为"源节点":

  1. 1

    在目标节点上将哈希槽设置为导入状态

    CLUSTER SETSLOT <slot> IMPORTING <source-node-id>
  2. 2

    在源节点上将哈希槽设置为迁移状态

    CLUSTER SETSLOT <slot> MIGRATING <destination-node-id>
  3. 3

    从源节点获取键并迁移到目标节点

    CLUSTER GETKEYSINSLOT slot count
    MIGRATE host port destination-db timeout [KEYS key [key ...]]
  4. 4

    在目标节点上更新槽所属信息

    CLUSTER SETSLOT <slot> NODE <destination-node-id>
  5. 5

    在源节点上更新槽所属信息

    CLUSTER SETSLOT <slot> NODE <destination-node-id>

    清除NODE的MIGRATING状态,并更新SLOT所属信息。

  6. 6

    (可选)通知其他主节点

    CLUSTER SETSLOT <slot> NODE <destination-node-id>

    让其他节点尽快知道SLOT所属的更新。可选步骤,信息会通过gossip协议在集群中传播。

graph TB subgraph "迁移过程" A[源节点] -->|1. 设置MIGRATING| A1[源节点
MIGRATING状态] B[目标节点] -->|1. 设置IMPORTING| B1[目标节点
IMPORTING状态] A1 -->|2. GETKEYSINSLOT| A2[获取key列表] A2 -->|3. MIGRATE| B1 A1 -->|4. 更新SLOT所属| A3[源节点
SLOT更新] B1 -->|4. 更新SLOT所属| B2[目标节点
SLOT更新] end style A fill:#f3e5f5,stroke:#333,stroke-width:2px style B fill:#e3f2fd,stroke:#333,stroke-width:2px style A1 fill:#f3e5f5,stroke:#333,stroke-width:2px style A2 fill:#f3e5f5,stroke:#333,stroke-width:2px style A3 fill:#f3e5f5,stroke:#333,stroke-width:2px style B1 fill:#e3f2fd,stroke:#333,stroke-width:2px style B2 fill:#e3f2fd,stroke:#333,stroke-width:2px

自动迁移工具

还可以使用redis-cli工具的reshard命令,只需要输入源节点、目标节点、slot,它会按步骤自动执行以上命令。

分步讲解

第1步:准备目标节点

确保目标节点在"源节点配置为重定向"之前已准备好接受 ASK 重定向。这时目标节点会接收属于待迁移SLOT的key的命令,但需要在正常的命令前加上ASKING指令,确保这条命令是由源节点ask重定向的;如果没有带ASKING指令,会返回一个moved错误并附带源节点信息,通知client先去请求源节点。

第2步:准备源节点

将源节点设置为迁移中,这时源节点负责接收所有属于迁移SLOT的key的命令,当key在源节点时,进行命令执行;当key不在源节点,将向client返回ask错误并附带目标节点信息,client在收到ask后,会使用附带的目标节点信息进行命令重试(除了正常的命令,还需要在前面增加一条ASKING指令,来告知目标节点,这是一条ask重定向的命令)

IMPORTING和MIGRATING状态的作用

IMPORTING和MIGRATING是为了先将目标节点和源节点设置为迁移中状态。key迁移的过程中,一定会是部分key在源节点、部分key在目标节点。这时集群的NODE信息中,迁移中的SLOT仍然被源节点负责。当源节点发现key不在当前节点时,会下发ask指令通知client去目标节点重试。

目标节点只能接受来自ask重定向的命令(需要ASKING指令),证明client先从源节点获取了ask重定向。这样就保证了迁移过程中的命令正确执行。

客户端连接

Redis Cluster客户端需要处理重定向和集群拓扑变更,主要有两种连接模式:

直连模式

  • 客户端直接连接集群中的每个节点
  • 客户端自己维护槽位与节点的映射关系
  • 客户端自行处理重定向逻辑
  • 适合长连接场景,性能更好

代理模式

  • 客户端连接到代理服务器(如Twemproxy、Codis)
  • 代理服务器维护槽位与节点的映射关系
  • 代理服务器处理重定向逻辑
  • 适合短连接场景,对客户端友好
graph TB subgraph "直连模式" C1[客户端] -->|连接| N1[节点1] C1 -->|连接| N2[节点2] C1 -->|连接| N3[节点3] end subgraph "代理模式" C2[客户端] -->|连接| P[代理服务器] P -->|连接| N4[节点1] P -->|连接| N5[节点2] P -->|连接| N6[节点3] end style C1 fill:#f9f9f9,stroke:#333,stroke-width:2px style C2 fill:#f9f9f9,stroke:#333,stroke-width:2px style P fill:#e1f5fe,stroke:#333,stroke-width:2px style N1 fill:#e8f5e9,stroke:#333,stroke-width:2px style N2 fill:#e8f5e9,stroke:#333,stroke-width:2px style N3 fill:#e8f5e9,stroke:#333,stroke-width:2px style N4 fill:#e8f5e9,stroke:#333,stroke-width:2px style N5 fill:#e8f5e9,stroke:#333,stroke-width:2px style N6 fill:#e8f5e9,stroke:#333,stroke-width:2px

客户端实现要点

拓扑发现

客户端可以通过 CLUSTER SLOTSCLUSTER NODES 命令获取集群拓扑信息,并定期刷新。

重定向处理

客户端需要处理 MOVED 和 ASK 重定向,MOVED 重定向后需要更新本地槽位映射,ASK 重定向则是临时性的。

连接池管理

为每个节点维护连接池,根据键所在的槽位选择正确的节点连接。

错误处理

处理节点故障、网络异常等情况,实现自动重试和故障转移。

多key操作

Redis Cluster对多key操作有一定限制,因为不同的key可能分布在不同的节点上。

多key操作限制

在Redis Cluster中,多key操作(如MGET、MSET、事务等)只有在所有key都位于同一个槽时才能正常执行。

如果多个key分布在不同的槽中,Redis会返回错误:CROSSSLOT Keys in request don't hash to the same slot

哈希标签

Redis提供了哈希标签(Hash Tags)机制,可以强制多个key映射到同一个槽中。

当key包含 {...} 时,只有花括号内的部分会被用来计算哈希值。例如:user:{123}:profileuser:{123}:orders 会被映射到同一个槽。

graph LR K1[user:123:profile] -->|CRC16| S1[不同槽] K2[user:456:profile] -->|CRC16| S2[不同槽] K3["user:{123}:profile"] -->|"CRC16 {123}"| S3[相同槽] K4["user:{123}:orders"] -->|"CRC16 {123}"| S3 style K1 fill:#f9f9f9,stroke:#333,stroke-width:2px style K2 fill:#f9f9f9,stroke:#333,stroke-width:2px style K3 fill:#e1f5fe,stroke:#333,stroke-width:2px style K4 fill:#e1f5fe,stroke:#333,stroke-width:2px style S1 fill:#e8f5e9,stroke:#333,stroke-width:2px style S2 fill:#e8f5e9,stroke:#333,stroke-width:2px style S3 fill:#e8f5e9,stroke:#333,stroke-width:2px

客户端多key操作解决方案

  1. 使用哈希标签确保相关键在同一个槽
  2. 客户端分批发送命令到不同节点并合并结果
  3. 使用Lua脚本(脚本中的所有key必须在同一个节点)

总结

优势

  • 水平扩展能力强
  • 去中心化架构,无单点故障
  • 支持在线数据迁移
  • 自动故障检测和故障转移
  • 高性能和高可用性

局限性

  • 多key操作受限
  • 客户端复杂度增加
  • 数据迁移过程中性能可能下降
  • 节点间通信开销较大
  • 配置管理相对复杂

最佳实践

  • 合理规划槽位分配
  • 使用哈希标签管理相关数据
  • 定期备份和监控集群状态
  • 避免大key和热点key
  • 选择合适的客户端库

核心概念回顾

  • 哈希槽(Slot): Redis Cluster将键空间分为16384个哈希槽,每个节点负责一部分槽。
  • 节点通信: 节点间通过Gossip协议进行信息同步,每个节点都与其他节点建立TCP连接。
  • MOVED重定向: 当键不在当前节点时,返回MOVED错误并指向正确的节点,客户端需要更新拓扑信息。
  • ASK重定向: 在槽迁移过程中,源节点返回ASK错误指向目标节点,客户端需要发送ASKING命令。
  • 在线迁移: 通过IMPORTING和MIGRATING状态,实现不停机的数据迁移。
  • 哈希标签: 使用{...}语法强制多个键映射到同一个槽,便于多键操作。

Redis Cluster是一个功能强大的分布式解决方案,通过深入理解其架构设计和工作原理,可以更好地利用它来构建高性能、高可用的分布式缓存系统。

参考资料