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区间。
节点属性
每个节点本地都缓存了集群中其他节点的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)
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)
节点加入示例
例如当新增一个节点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>对应关系表,后续的命令仍然向源节点发送。
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
在目标节点上将哈希槽设置为导入状态
CLUSTER SETSLOT <slot> IMPORTING <source-node-id>
-
2
在源节点上将哈希槽设置为迁移状态
CLUSTER SETSLOT <slot> MIGRATING <destination-node-id>
-
3
从源节点获取键并迁移到目标节点
CLUSTER GETKEYSINSLOT slot count
MIGRATE host port destination-db timeout [KEYS key [key ...]]
-
4
在目标节点上更新槽所属信息
CLUSTER SETSLOT <slot> NODE <destination-node-id>
-
5
在源节点上更新槽所属信息
CLUSTER SETSLOT <slot> NODE <destination-node-id>
清除NODE的MIGRATING状态,并更新SLOT所属信息。
-
6
(可选)通知其他主节点
CLUSTER SETSLOT <slot> NODE <destination-node-id>
让其他节点尽快知道SLOT所属的更新。可选步骤,信息会通过gossip协议在集群中传播。
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)
- 代理服务器维护槽位与节点的映射关系
- 代理服务器处理重定向逻辑
- 适合短连接场景,对客户端友好
客户端实现要点
拓扑发现
客户端可以通过 CLUSTER SLOTS
或 CLUSTER 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}:profile
和 user:{123}:orders
会被映射到同一个槽。
客户端多key操作解决方案
- 使用哈希标签确保相关键在同一个槽
- 客户端分批发送命令到不同节点并合并结果
- 使用Lua脚本(脚本中的所有key必须在同一个节点)
总结
优势
- 水平扩展能力强
- 去中心化架构,无单点故障
- 支持在线数据迁移
- 自动故障检测和故障转移
- 高性能和高可用性
局限性
- 多key操作受限
- 客户端复杂度增加
- 数据迁移过程中性能可能下降
- 节点间通信开销较大
- 配置管理相对复杂
最佳实践
- 合理规划槽位分配
- 使用哈希标签管理相关数据
- 定期备份和监控集群状态
- 避免大key和热点key
- 选择合适的客户端库
核心概念回顾
- 哈希槽(Slot): Redis Cluster将键空间分为16384个哈希槽,每个节点负责一部分槽。
- 节点通信: 节点间通过Gossip协议进行信息同步,每个节点都与其他节点建立TCP连接。
- MOVED重定向: 当键不在当前节点时,返回MOVED错误并指向正确的节点,客户端需要更新拓扑信息。
- ASK重定向: 在槽迁移过程中,源节点返回ASK错误指向目标节点,客户端需要发送ASKING命令。
- 在线迁移: 通过IMPORTING和MIGRATING状态,实现不停机的数据迁移。
- 哈希标签: 使用{...}语法强制多个键映射到同一个槽,便于多键操作。
Redis Cluster是一个功能强大的分布式解决方案,通过深入理解其架构设计和工作原理,可以更好地利用它来构建高性能、高可用的分布式缓存系统。