1. Redis Cluster分片核心机制

Redis Cluster 是 Redis 官方提供的分布式分片方案,它把整个键空间划分为 16384 个哈希槽(hash slot),每个主节点负责其中一部分槽位。客户端写入一个键时,先对键名做 CRC16 计算,再对 16384 取模,得到该键所属的槽号,然后由持有该槽的节点处理。理解这套机制,是后续做扩缩容、槽迁移和故障转移的前提。

一、哈希槽:分片的最小单位

槽号的计算公式为:

slot = CRC16(key) mod 16384

这里有一个关键细节:如果键名中包含 {...},则只对花括号内的内容做哈希,这称为 hash tag。例如 user:{1001}:name 和 user:{1001}:age 的哈希对象都是 1001,因此落在同一个槽、同一个节点上。这让需要原子性多键操作(如 MGET、事务、Lua 脚本)的键可以强制共槽。

16384 这个数字不是随意的。集群节点之间通过心跳包交换槽位信息,每个节点用一张 16384 位的位图(bitmap)表示自己负责的槽,即 2KB。如果槽数取 65536,位图就是 8KB,心跳包会明显变大。在集群规模通常不超过 1000 个节点的前提下,16384 已经足够均匀,同时把通信开销压到较低水平。

槽的分配结果由集群元数据维护。每个节点都保存一份完整的槽位映射表,记录“哪个槽属于哪个节点”。客户端访问任意节点,如果该节点不持有目标槽,会返回 MOVED 重定向;如果槽正在迁移,则可能返回 ASK 重定向。两者的区别在于:MOVED 表示槽的归属已经永久改变,客户端应更新本地槽映射缓存;ASK 表示只是临时到目标节点取一次数据,不更新缓存。

二、节点通信协议与集群总线

集群中的每个节点都开放两个端口:

  • 客户端端口:默认 6379,用于处理普通命令,如 GET、SET。
  • 集群总线端口:默认是客户端端口 + 10000,即 16379,用于节点之间的二进制协议通信。

集群总线使用一套独立的二进制协议,而不是 RESP 文本协议。节点通过它交换以下几类消息:

  • MEET:邀请一个新节点加入集群。
  • PING / PONG:心跳探测,携带自身状态、负责的槽位、配置纪元(config epoch)等信息。
  • FAIL:广播某个节点已客观下线(fail)。
  • PUBLISH:在集群范围内传播 Pub/Sub 消息。
  • UPDATE:通知槽位归属变更。

每个节点每秒会向随机挑选的几个节点发送 PING,同时也会向最久未通信的节点发送 PING。收到 PONG 后,双方刷新对彼此的“最后通信时间”。如果某个节点在 cluster-node-timeout(默认 15000 毫秒)内一直没有响应,就会被标记为疑似下线(PFAIL);当超过半数主节点都报告该节点 PFAIL 时,它被判定为客观下线(FAIL),随后触发故障转移。

节点之间还通过 Gossip 协议 传播信息。每个 PING/PONG 包会附带随机几个其他节点的状态,这样集群状态能在有限跳数内扩散到全网,而不需要中心节点。这也是 Redis Cluster 去中心化设计的核心。

三、拓扑结构与槽分配示例

一个典型的三主三从集群如下:节点 A、B、C 为主节点,A1、B1、C1 分别为它们的从节点。槽的分配可以是:

主节点负责槽范围从节点
A0 – 5460A1
B5461 – 10922B1
C10923 – 16383C1

从节点默认只复制主节点数据,不处理写请求。当主节点被判定 FAIL 后,其从节点会发起选举,获得多数主节点投票后晋升为新主节点,接管原主节点的槽位,并广播 UPDATE 消息。整个过程由集群总线完成,客户端只需根据 MOVED 重定向更新路由。

下面这张图展示了三主三从集群中,客户端请求如何经过槽计算、节点路由,以及节点之间通过集群总线交换心跳与槽位信息的关系。

客户端 CRC16(key) mod 16384 请求槽 100 主节点 A 槽 0 – 5460 端口 6379 / 16379 持有槽 100 → 直接处理 主节点 B 槽 5461 – 10922 端口 6380 / 16380 不持有槽 100 → MOVED 主节点 C 槽 10923 – 16383 端口 6381 / 16381 不持有槽 100 → MOVED 从节点 A1 复制 A 从节点 B1 复制 B 从节点 C1 复制 C 集群总线 端口 +10000 PING / PONG MEET FAIL PUBLISH UPDATE Gossip 传播 槽位位图 2KB config epoch 图例 客户端请求 主从复制 集群总线通信

四、槽迁移与重定向的配合

扩容时,需要把部分槽从旧节点迁移到新节点。迁移以槽为单位,使用 CLUSTER SETSLOT、CLUSTER GETKEYSINSLOT、MIGRATE 等命令完成。迁移过程中,源节点对已迁移的键返回 ASK 重定向,对尚未迁移的键正常处理;目标节点则临时接受 ASKING 请求。迁移完成后,通过 CLUSTER SETSLOT ... NODE ... 把槽的归属正式交给新节点,此后客户端收到 MOVED 并更新路由表。

实践提示:客户端应缓存槽位映射表,收到 MOVED 后刷新对应槽的映射;收到 ASK 时只对当次请求加 ASKING 前缀重试,不修改缓存。这样既能减少重定向次数,又不会在迁移期间把临时状态误当成永久归属。

五、集群总线与故障判定

集群总线不仅传递心跳,还承担故障判定和配置传播。每个节点维护一个“最后通信时间”表,超过 cluster-node-timeout 未收到 PONG 就标记 PFAIL。PFAIL 只是本地判断,需要通过 Gossip 扩散,当多数主节点都认为某节点 PFAIL 时,才升级为 FAIL。FAIL 消息一旦广播,该节点的从节点就会发起选举,获得多数主节点投票后晋升,接管槽位并广播 UPDATE。

这套机制的关键参数是 cluster-node-timeout。设置过小,网络抖动容易误判下线,引发不必要的故障转移;设置过大,真实故障时切换变慢,影响可用性。一般建议根据实际网络 RTT 和业务容忍度在 5000~30000 毫秒之间调整,并配合 cluster-replica-validity-factor 控制从节点参与选举的资格。

总结来看,Redis Cluster 的分片核心可以归纳为三点:槽是分片的最小单位,16384 个槽通过 CRC16 映射键;节点通过集群总线交换状态,用 Gossip 扩散槽位和故障信息;客户端通过 MOVED/ASK 重定向找到正确节点。理解这三者的配合,才能在扩缩容、迁移和故障转移时做出正确判断。