16384 个哈希槽
Redis Cluster 不用一致性哈希环,而是把整个键空间切成 16384 个哈希槽。每个 key 用 CRC16(key) mod 16384 算出落在哪个槽,槽归某个分片所有——于是路由变成一次纯计算,没有中心节点、没有代理。
把键空间分散到多台机器
当数据量或写入速率超过单台 Redis 的极限,就得把键空间横向切分到多台机器上。问题是:给定一个 key,怎么快速、确定地知道它在哪台机器?
常见方案是一致性哈希环,但 Redis 选了更简单、更可控的一招:预先把键空间划成 16384 个固定的哈希槽(slot)。每个 key 属于某一个槽,每个槽归某一个主分片所有。加机器、减机器,本质上都只是在分片之间搬动槽而已。
不是给每个包裹现算地址,而是先把城市划成 16384 个邮政编码区。每个包裹按规则算出邮编,邮编归哪个分拣中心管是固定的。要新开一个分拣中心,只需把一部分邮编划过去——包裹本身的算法从不改变。
槽的计算:CRC16 取模
槽的计算是纯确定性的——同一个 key,在任何客户端、任何时刻,算出的槽都一样:
# CLUSTER KEYSLOT foo → 12182 CLUSTER KEYSLOT bar → 5061
因为 16384 = 2¹⁴,取模可以退化成一次按位与(& 0x3FFF),快到可以忽略。智能客户端在连接时缓存一份「槽 → 节点」映射,之后本地算槽、直连目标分片,不经任何中转。
哈希标签:把相关的 key 摁到同一个槽
Redis Cluster 只允许对同一个槽内的多个 key 做多键操作(MSET、MGET 等),否则报 CROSSSLOT。想让一组 key 落在一起,用哈希标签:只要 key 里有 {…},就只对花括号里的部分求哈希。
order:100 → slot B
MSET/MGET 会被拒绝。{user:100}.orders → slot X
user:100,两个 key 必然同槽、同分片,多键操作与事务可用。哈希标签是把双刃剑。 用它可以共置相关数据;但如果把太多 key 塞进同一个标签,会让某个槽(进而某个分片)变成热点,反而拖垮分布均衡。
扩缩容就是在分片间迁移槽
每个主分片负责 16384 个槽中的一段。要扩容,就把一部分槽从旧分片迁到新分片;要缩容,就把槽并回去。数据随槽一起搬。
在 Redis Cloud / Redis Enterprise 上,重分片、槽迁移和自动再平衡都是托管自动完成的,扩缩容不影响应用;社区版则需要用 redis-cli --cluster reshard 手动操作。
MOVED 与 ASK:两种重定向
如果客户端把命令发给了「不拥有这个槽」的节点,节点不会转发,而是回一个重定向,告诉客户端该找谁。但这里有个微妙而关键的区别。
MOVED 是永久的:它说「这个槽现在归 C 了」。智能客户端收到后会更新本地映射,以后直接发 C。这通常发生在扩缩容、故障切换之后拓扑变化时。
ASK 则是临时的,只在某个槽正在迁移途中出现——迁出节点标 MIGRATING、迁入节点标 IMPORTING。当你要的 key 已经搬到目标节点,源节点回 ASK:客户端要先发一个 ASKING,再把命令发给目标节点,而且不更新映射(因为槽还没正式易主)。
动作:更新槽映射,以后直连新节点。
时机:扩缩容 / 故障切换后。
动作:发
ASKING + 命令给目标,不更新映射。时机:重分片进行中。
为什么槽数是 16384
CRC16 明明能产生 65536 种取值,为什么槽只取 16384 = 2¹⁴,而不是 65536?答案藏在节点间的心跳包里。
集群里每个节点都通过集群总线(端口 = 数据端口 + 10000,如 6379→16379)用 gossip 协议互相心跳,心跳里带着一张它拥有哪些槽的位图。位图大小 = 槽数 ÷ 8:
再加上一个现实判断:Redis Cluster 的节点数实际很难超过约 1000。16384 个槽已经能让上千个节点分得足够均匀,同时把心跳位图压到 2KB——这是分布粒度与心跳开销之间的一个务实折中。理论上最多可有 16384 个主分片(每个 1 槽),但推荐规模在千级以内。
使用集群前的注意事项
路由是一次计算,而非查询
16384 个固定的槽,让「key 在哪台机器」从一次查询变成一次 CRC16 mod 16384。没有代理、没有中心目录;扩缩容只是搬槽,客户端靠 MOVED/ASK 自我纠正。简单,所以可扩展。


