04.4 - 数据分片与哈希槽
定位: Redis Cluster 的数据分布策略——16384 个哈希槽如何划分和管理数据 面试高频度: ⭐⭐⭐⭐ 考查方式: 哈希槽计算、为什么是 16384、重分片过程、哈希标签
一、这是什么?为什么需要它?
是什么
哈希槽(Hash Slot)是 Redis Cluster 的数据分片单元:将整个键空间分成 16384 个槽,每个 key 通过 CRC16 计算归属到一个槽,再将槽分配给集群节点管理。
为什么需要哈希槽?
一致性哈希的问题: 一致性哈希(如 Memcached 客户端实现):hash(key) 落在环上,找到最近的节点
- 问题 1:节点增减 → 部分 key 重新映射(缓解:虚拟节点,增加管理复杂度)
- 问题 2:数据分布可能不均(缓解:虚拟节点,还是复杂)
- 问题 3:客户端各自实现一致性哈希 → 不一致
哈希槽方案的优势: Redis Cluster 的固定 16384 槽:
- 槽到节点的映射由集群管理,客户端只需缓存
- 迁移以槽为单位 → 粒度适中(不会迁移单个 key)
- 数据分布均匀(CRC16 的分布特性)
- 迁移过程可监控(每个槽的迁移进度)
二、原理拆解
2.1 CRC16 与槽计算
槽的计算公式:
slot = CRC16(key) % 16384CRC16 的特性:
- CRC16 不是简单的 hash,而是一个校验和算法
- 输入:key 的字符串,输出:16 位整数(0-65535),取模 16384:0-16383
- 分布均匀性: key 的微小变化 → slot 完全不同
- "user:1" → slot 1234, "user:2" → slot 5678, "user:3" → slot 9012
测试分布:
bashredis-cli -p 7000 CLUSTER KEYSLOT "user:1001" redis-cli -p 7000 CLUSTER KEYSLOT "user:1002" redis-cli -p 7000 CLUSTER KEYSLOT "order:20240101" # 输出不同的 slot → 分布到不同节点
2.2 为什么必须是 16384 个槽?
这是一个经过设计的选择:
影响选择 16384 的三个因素:
心跳包大小
- Cluster 节点间 Gossip 消息包含节点的 slot 位图(16384 bit = 2KB)
- 如果用 65536 个槽(CRC16 的理论最大值)→ 位图 8KB → 心跳消息更大 → 网络开销增加
槽数量的合理性
- 集群规模通常 < 1000 节点,16384 槽 ÷ 1000 节点 ≈ 16 槽/节点 → 足够均衡
- 更少节点时(如 3 主),每节点 ~5461 槽 → 依然均衡
带宽 vs 粒度权衡
- 更多槽 → 更精细的迁移 → 更多元数据
- 16384 被认为是在合理粒度下的最优选择
官方设计决策: 16384 槽 / 集群 = 合理的心跳包大小 + 合理的分片粒度 + CRC16 取模不增加额外计算成本
2.3 哈希标签(Hash Tag)
规则:
- 找到 key 中第一个
{和第一个}之间的内容,用该内容计算 CRC16- 没有
{}→ 整个 key 计算 CRC16
2.4 重分片(Resharding)过程
迁移期间:
- GET key → ASK 重定向(Node A → Node D)
- SET key → ASK 重定向(Node A → Node D)
- 直到迁移完成 → MOVED(槽正式归属新节点)
三、图解全景
迁移命令:
bashredis-cli --cluster reshard 127.0.0.1:7000→ 交互式:选择迁移数量、目标节点、源节点 → 自动分步迁移槽和数据
四、实战验证
4.1 计算哈希槽
bash
redis-cli -p 7000
# 1. 计算 key 的 slot
127.0.0.1:7000> CLUSTER KEYSLOT "user:1001"
(integer) 1234
127.0.0.1:7000> CLUSTER KEYSLOT "user:1002"
(integer) 5678
# 2. Hash Tag 示例
127.0.0.1:7000> CLUSTER KEYSLOT "{user}:1001"
(integer) 2345
127.0.0.1:7000> CLUSTER KEYSLOT "{user}:1002"
(integer) 2345 # 相同!4.2 查看槽分配
bash
# 查看所有槽的分配
redis-cli -p 7000 CLUSTER SLOTS
# 1) 1) (integer) 0
# 2) (integer) 5460
# 3) 1) "127.0.0.1"
# 2) (integer) 7000
# 3) "node_id_xxx"
# 4) 1) "127.0.0.1"
# 2) (integer) 7003 ← 从节点
# 2) 1) (integer) 5461
# 2) (integer) 10922
# 3) 1) "127.0.0.1"
# 2) (integer) 7001
# ...
# 查看槽计数(每个节点多少槽)
redis-cli -p 7000 CLUSTER INFO
# cluster_size:34.3 执行重分片
bash
# 交互式重分片
redis-cli --cluster reshard 127.0.0.1:7000
# How many slots do you want to move? 4096
# What is the receiving node ID? <Node D ID>
# Source node #1: all
# ... 自动迁移
# 或一键迁移(从 7000 迁 100 个槽到 7001)
redis-cli --cluster reshard 127.0.0.1:7000 \
--cluster-from <7000-node-id> \
--cluster-to <7001-node-id> \
--cluster-slots 100 \
--cluster-yes
# 迁移后验证
redis-cli -p 7000 CLUSTER INFO | grep cluster_slots_ok
# cluster_slots_ok:16384 # 所有槽正常4.4 验证 Hash Tag 的多 key 操作
bash
# 使用 Hash Tag 让多 key 在同一节点
redis-cli -c -p 7000
# 不用 Hash Tag → 不同节点
127.0.0.1:7000> MSET user:1 "Alice" user:2 "Bob"
(error) CROSSSLOT Keys in request don't hash to the same slot
# 用 Hash Tag → 同节点,支持 MSET
127.0.0.1:7000> MSET {user}:1 "Alice" {user}:2 "Bob"
OK # 成功
# 验证相同 slot
127.0.0.1:7000> CLUSTER KEYSLOT {user}:1
(integer) 2345
127.0.0.1:7000> CLUSTER KEYSLOT {user}:2
(integer) 2345 # 相同!五、面试视角
| 追问 | 答案要点 |
|---|---|
| 为什么是 16384 个槽而不是更多? | 心跳包携带槽位图(16384 bit=2KB),65536 个槽=8KB,网络开销大;16384 对 ≤1000 节点足够均衡 |
| Hash Tag 的作用? | {} 内的部分作为 CRC16 输入,让多个 key 落在同一节点,支持 MSET/SINTER 等跨 key 操作 |
| 重分片过程中数据一致性如何保证? | 迁移时源节点标记 migrating,先读源节点;目标节点标记 importing,ASK 重定向保证不丢数据 |
| CRC16 为什么比简单 hash 好? | 分布均匀、算法效率高(硬件级支持)、16 位输出刚好对 16384 取模 |
| 重分片会影响服务吗? | 迁移期间会触发 ASK 重定向(多一次网络跳),但数据不会丢失,业务无感(慢一点) |
| 槽分配不均怎么办? | redis-cli --cluster rebalance 自动均衡;或手动 reshard 调整 |
| 客户端怎么知道 MOVED? | 智能客户端(JedisCluster/LettuceCluster)维护 slot_cache,收到 MOVED 后自动更新 |
📚 相关链接
- **Redis Cluster** — 哈希槽在 Cluster 中的角色
- ← 返回 **高可用与集群索引**