Redis 哈希槽(Hash Slot)详解:从零理解 Redis Cluster 的数据分片核心
在 Redis Cluster(集群模式)中,哈希槽 是实现数据分片、高可用与线性扩展的基石。很多开发者刚接触集群时,对"16384 个槽"、"槽位路由"、"节点宕机如何迁移"等概念感到困惑。本文将用通俗的语言 + 流程图 + 实例,彻底讲清哈希槽的设计思想与工作原理。
一、为什么需要哈希槽?
单个 Redis 实例的内存、吞吐量有上限。为了存储更多数据、支撑更高 QPS,自然想到 将数据分散到多个 Redis 节点 ------ 这就是分布式分片。
分片面临三个核心问题:
- 数据如何均匀分布 到不同节点?
- 客户端如何知道 某个 key 应该去哪个节点访问?
- 节点增减/故障 时如何尽量减少数据迁移?
哈希槽正是 Redis Cluster 解决上述问题的答案。
二、哈希槽的基本概念
Redis Cluster 将整个数据空间固定划分为 16384 个哈希槽 (Hash Slot),编号从 0 到 16383。
核心公式:
hash_slot = CRC16(key) % 16384
CRC16(key)对 key 进行循环冗余校验,得到一个 16 位整数(0~65535)- 对 16384 取模,结果必然落在 0~16383 之间
为什么是 16384?
Redis 作者解释过:16384 是一个适中的数字,每个槽的元数据用 2KB 的 bitmap 即可描述(16384 bits = 2KB),且能满足绝大多数集群规模(最大 1000 节点,平均每个节点 16 个槽)。
三、节点与槽的映射关系
每个 Redis 节点负责一部分连续的(或不连续)哈希槽。例如一个 3 节点的集群可能分布为:
| 节点 | 负责的哈希槽范围 |
|---|---|
| Node A | 0 -- 5460 |
| Node B | 5461 -- 10922 |
| Node C | 10923 -- 16383 |
集群内部维护一个 槽→节点 的映射表,每个节点都知道所有槽分别由谁负责(通过 CLUSTER NODES 命令可查看)。
可视化:槽分布示例
33% 33% 33% 哈希槽分布示例 (3节点) Node A (0-5460) Node B (5461-10922) Node C (10923-16383)
四、数据路由流程(正常情况)
当客户端发送一个命令(如 GET mykey)时,流程如下:
负责该槽的节点 任意节点(入口) 客户端 负责该槽的节点 任意节点(入口) 客户端 1. GET mykey 2. 计算 CRC16("mykey") % 16384 = 5200 3. 查本地槽映射表:槽5200由 NodeB 负责 4. 返回 MOVED 5200 127.0.0.1:6379 5. 重定向到 NodeB 执行 GET mykey 6. 返回结果
注意:如果客户端一开始连接的就是正确节点,则直接返回数据,无 MOVED 重定向。智能客户端(如 JedisCluster)会缓存槽映射,减少重定向次数。
五、故障转移:节点宕机时哈希槽如何处理?
Redis Cluster 的高可用依赖于主从架构:每个主节点负责若干槽,并配有至少一个从节点。当主节点宕机时:
是
是
否
主节点 M1 宕机
集群中超过半数主节点
认为 M1 失联?
从节点 S1 发起选举
获得多数主节点投票?
S1 晋升为新的主节点
S1 接管 M1 原来的所有槽
集群恢复,槽映射更新
集群标记 M1 为 FAIL,
但暂不切换
核心点 :槽是绑定在主节点上的逻辑单元,主节点故障后,其从节点会原封不动地继承那些槽,客户端无需感知槽位的变化(只需获取新的 MOVED 重定向)。
如果某个槽既没有主节点也没有从节点(所有相关节点全挂),则整个集群不可用 ------ 因为一部分数据无法访问。
六、节点扩容/缩容时的槽迁移
增加或删除节点时,哈希槽需要在节点间重新分配 ,并且槽内的 key 也要随之迁移。
迁移过程(简化):
源节点导出槽
目标节点导入槽
逐个迁移key
迁移完成
更新集群槽映射
Redis 提供了 CLUSTER SETSLOT、CLUSTER GETKEYSINSLOT 等命令以及 redis-cli --cluster rebalance 工具来平滑迁移。迁移过程中,客户端访问正在迁移的槽时,可能会收到 ASK 重定向,与 MOVED 类似但属于临时状态。
七、哈希槽 vs 一致性哈希
| 对比项 | 一致性哈希(如 Redis Cluster 前的老方案) | Redis 哈希槽 |
|---|---|---|
| 数据分片方式 | 哈希环 + 虚拟节点 | 固定 16384 个槽 |
| 节点增减影响范围 | 只影响相邻节点,但数据迁移粒度较粗 | 精确控制每个槽的迁移 |
| 元数据大小 | 虚拟节点映射表可能较大 | 16384 长度的 bitmap,极紧凑 |
| 实现复杂度 | 较高(需维护环) | 较低,槽映射清晰 |
| 客户端路由 | 需计算哈希并查找环 | 计算槽位,查表即可 |
哈希槽的优势:简单、确定性强、迁移粒度可控、元数据极小(每个节点只需知道 2KB 的位图即可描述全部槽的归属)。
八、常见面试题与解答
问:Redis Cluster 中一个 key 到底存在哪个节点?
答:先对 key 做 CRC16 取模得到槽号,再根据集群内部槽→节点映射表找到负责该槽的主节点。
问:为什么哈希槽数量是 16384,而不是 65536 或其他?
答:16384 刚好是 2^14,每个槽用 1 bit 表示归属,16384 bits = 2KB。若用 65536 bits = 8KB,心跳包会更大;且 Redis 节点数量通常不会超过 1000,16384 个槽足够均匀分配。
问:如果客户端缓存了槽映射,但集群发生了故障转移或重新分片,会怎样?
答:客户端按旧映射访问节点时,节点会返回 MOVED 错误,并告知新地址。智能客户端会更新本地缓存,下次直接访问正确节点。
问:能否手动将某个槽从一个节点移到另一个节点?
答:可以。使用 CLUSTER SETSLOT <slot> NODE <node_id> 命令,但需要先迁移槽内的所有 key(否则会丢失数据)。生产环境推荐使用 redis-cli --cluster reshard。
九、代码示例:计算 key 的槽位
python
# Python 示例:计算任意 key 属于哪个哈希槽
def get_slot(key):
import binascii
# 模拟 Redis CRC16 实现(实际 Redis 有自己的 CRC16 查表算法)
crc = binascii.crc_hqx(key.encode('utf-8'), 0xFFFF)
return crc % 16384
print(get_slot("user:1001")) # 输出例如 4210
print(get_slot("order:2024")) # 输出例如 12987
在 Redis 客户端中,可以直接使用 CLUSTER KEYSLOT <key> 命令查看:
bash
> CLUSTER KEYSLOT user:1001
(integer) 4210
十、总结
- 哈希槽是 Redis Cluster 的数据分片基础 :固定 16384 个槽,通过
CRC16(key) % 16384映射。 - 每个槽由唯一一个主节点负责,可以迁移给其他节点(扩容、缩容、故障转移)。
- 客户端访问流程:计算槽 → 查映射表 → 直接访问负责节点,错误时收到 MOVED/ASK 重定向。
- 高可用:主节点宕机时,从节点自动继承其槽并升级为主节点。
- 对比传统分片方案:哈希槽设计更简洁、元数据更小、迁移粒度精细。
如果你正在搭建 Redis 集群,请牢记:槽均匀,数据才均匀;槽映射稳定,集群才稳定。