为什么 Redis Cluster 要设计 16384 个槽?答案比你想的聪明

71 阅读8分钟



大家好,我是 31 岁的小米,一个热爱折腾技术、喜欢一边踩坑一边做咖啡的程序员。

前段时间陪朋友准备 Java 社招面试,他问了我一句:

“小米,Redis 的哈希槽到底是怎么回事?我每次面试都被问,答得似懂非懂……”

我一听,熟悉的面试 PTSD 猛地从脑子里蹿出来,仿佛又看到那年面试官微微抬起眼镜、露出“我就等你卡壳”的表情。于是我拍了拍他肩膀说:

“兄弟,关于 Redis 哈希槽,我给你讲个故事 —— 听完你不仅懂了,还能反问面试官。”

先从一个小镇说起:16384 个房间的城市

想象一下,有一座神奇的小镇。镇上有 16384 间房间,编号从 0 到 16383。每一个房间,代表着 Redis Cluster 的一个“哈希槽”(Hash Slot)。

Redis 官方设计 Redis Cluster 时,决定不采用一致性哈希(像很多分布式系统那样),而是采用这种固定槽位机制。为什么?

因为一致性哈希迁移数据太麻烦,而槽位更像是房屋产权登记:只要告诉你“第 1000-2000 号房子属于 A 节点”,你就能快速定位。

这个世界观建立好之后,你会发现:

  • 数据不直接分配给节点
  • 而是分配给槽位(Slot)
  • 然后槽位再分配给不同的 Redis 节点

换句话说:

Redis Cluster 的本质不是“节点分数据”,而是“节点分槽,槽分数据”。

这一句话,面试官听了都得点头。

那数据怎么知道自己属于哪个槽?

继续想象:你把一把钥匙(Redis 的 Key)扔进这个小镇。小镇有一个超级强的“分拣机”,叫 CRC16 算法

它会对 key 做一次 CRC16 运算,得到一个数值。然后对这个数值进行取模:

slot = CRC16(key) % 16384

这个公式就是 Redis 哈希槽分配的核心规则。比如:

  • key = "user:1" → 可能落到槽 8120
  • key = "order:2023" → 可能落到槽 1520

然后Redis 就会根据槽位编号把请求路由到对应节点。你可以把它理解为:

这个世界上没有乱放的行李,每一个 key 都会分配到某个固定编号的房间里。

有序、有逻辑、不乱,Redis 就像是老城区里经验丰富的管理员。

问题来了:如果一个“大 key 族”必须放一起,那怎么办?

朋友听到这里问:

“那多个 key 想落到同一个槽,有办法吗?比如 Redis Pipeline 或 Lua 想用多个 Key 操作一个功能。”

我说:当然有,Redis 已经帮你想好办法了,那就是:使用 {} 哈希标签(Hash Tag)

你只需要把你想固定的部分放进大括号里,比如:

  • user:{1001}:profile
  • order:{1001}:list
  • cart:{1001}

无论 key 前后缀多长,Redis 只会对 {1001} 做哈希。于是,这几个 key 一定分配到同一个槽!原因是:

CRC16("user:{1001}:profile") = CRC16("{1001}")

在集群模式下,多个 key 必须落在同一个槽才能原子执行(如 MGET/MSET 或 Lua 脚本)。这也是为什么 Redis 必须提供哈希标签机制。

一句话总结:

{} 内的内容决定槽位,外面的不参与计算。

面试时能讲到这一点,面试官的眼镜会滑下来一点。

为什么是 16384?不是 1024 也不是 65536?

这个问题面试官也爱问。我带你走进 Redis 作者 antirez 的脑子:

1、太小不行(比如 1024)

容易导致扩容时槽太粗,数据迁移“块度”太大,不够灵活。

2、太大也不行(比如 65536)

主要是两个原因:

  1. 心跳消息变大。Redis Cluster 中节点之间要同步槽位映射表,槽太多会导致 Gossip 协议包超大。
  2. 性能浪费。过多槽位会导致维护成本上升,收益却不明显。

综合各种权衡,16384 是最佳选择:

  • 足够细
  • 映射表不会太大
  • 迁移粒度合适
  • 性能稳定

你完全可以把这个记成:

Redis 里,16384 是经过大量工程实战验证过的“甜蜜点”。

Redis 集群扩容时:槽是怎么搬家的?

这部分属于回答面试官时“加分项”。假设你现在有:

  • Node A:槽 0~5461
  • Node B:槽 5462~10922
  • Node C:槽 10923~16383

要新增一个节点 Node D,该怎么迁移槽?Redis 的做法是:

  1. 先把要迁移的槽位标记为“导入中”或“导出中”
  2. 槽位内的数据逐个迁移
  3. 迁移过程中,客户端如果访问数据:
    1. 可能被重定向到源节点
    2. 也可能被重定向到目标节点
  4. 全部迁移完成后,更新整个集群槽位映射表

整个过程依赖两个关键命令:

  • CLUSTER SETSLOT
  • MIGRATE

这套流程的本质是:不会让你因为迁移而中断服务。

只要你能把上面的流程讲出来,面试官心里会默默给你加分。

“重定向”机制:为什么我访问 Redis 会收到 MOVED 或 ASK?

当你访问一个 key,却访问到了错误节点时,Redis 会告诉你:

1、MOVED

  • 表示:“兄弟,你访问错地方了,这个槽已经永久搬到另一个节点了。”
  • MOVED 会让客户端 更新本地槽位映射表,以后别再访问错了。

2、ASK

  • 表示:“这个 key 正在搬家,但现在还没搬完,你暂时去另一个节点拿吧。”
  • 这是迁移过程中的临时状态。不会更新客户端本地映射表。

你可以把 MOVED 想象成:房东永久搬家了, 而 ASK 是:房东正在搬家中,你暂时去新家找他。

如果你能把这个比喻讲给面试官,他会心一笑。

为什么 Redis Cluster 非要用槽?

说个大白话,如果不用槽,会怎样?

  • 扩容/缩容时,需要遍历所有 key
  • 迁移成本巨大
  • 每个 key 的分布无法控制
  • 多 key 操作几乎无法保证落在一起

Redis 的槽机制让:

  • 数据分布均衡
  • 集群扩缩容简单
  • 路由精确
  • 多 key 操作可以指定落同槽
  • 整个 Redis Cluster 保持高性能

这就是 Redis 哈希槽存在的根本原因。

面试官最爱问的8个 Redis 哈希槽题目(附完美答法)

我送你一个小抄,你随便背一个,面试稳得不行。

1、Redis 为什么使用 16384 哈希槽?

因为槽位太少不够细、太多会增加节点间 Gossip 消息大小和维护成本。16384 是性能、灵活度、内存开销的最佳平衡点。

2、Key 如何映射到槽?

使用 CRC16 算法计算 Key 的校验值,再对 16384 取模。

3、哈希标签 {} 的作用?

使多个 key 映射到同一个槽,保证 MGET/MSET/Lua 等操作的原子性。

4、扩容时槽位怎么迁移?

通过 CLUSTER SETSLOT 设置槽状态为导入/导出,再用 MIGRATE 迁移数据,整个过程不会中断服务。

5、MOVED 和 ASK 的区别?

  • MOVED:表示槽永久迁移,客户端需要更新路由
  • ASK:临时迁移,客户端无需更新路由

6、Redis 为什么不直接采用“一致性哈希”?

因为槽机制更简单、路由更快、迁移粒度更细、有更好的稳定性。

7、一个节点能否管理非连续槽位?

可以。比如:Node A:0-100, 300-500, 800-900

8、槽位迁移时客户端如何知道?

通过 MOVED / ASK 重定向消息。

最后,用一句最容易记住的话总结哈希槽

我拍了拍朋友说:

“Redis Cluster 的哈希槽,就是 16384 个小房间,Key 用 CRC16 算法算出自己住哪间房;房间归节点管理,节点扩容时迁移房间,住户不会丢。”

朋友听完后突然眼睛一亮:

“我懂了!Redis 哈希槽不就是用来均衡数据、提高路由效率、简化扩容、保证多 key 操作落在一起的核心机制吗?”

我笑着说:

“对,就是这么一回事。你已经比 90% 的面试者懂更多了。”

END

如果你正在准备 Java 或后端社招,一定要理解 Redis Cluster 的工作原理。

哈希槽看似是一个名词,本质却是整个 Redis Cluster 世界观的地基。

理解之后,你会发现 Redis 的扩容、缩容、路由、负载均衡、宕机恢复等机制,都变得清晰了。

好朋友们,我们下期再见~

我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!