Skip to content

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) % 16384

CRC16 的特性:

  • CRC16 不是简单的 hash,而是一个校验和算法
  • 输入:key 的字符串,输出:16 位整数(0-65535),取模 16384:0-16383
  • 分布均匀性: key 的微小变化 → slot 完全不同
  • "user:1" → slot 1234, "user:2" → slot 5678, "user:3" → slot 9012

测试分布:

bash
redis-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 的三个因素:

  1. 心跳包大小

    • Cluster 节点间 Gossip 消息包含节点的 slot 位图(16384 bit = 2KB)
    • 如果用 65536 个槽(CRC16 的理论最大值)→ 位图 8KB → 心跳消息更大 → 网络开销增加
  2. 槽数量的合理性

    • 集群规模通常 < 1000 节点,16384 槽 ÷ 1000 节点 ≈ 16 槽/节点 → 足够均衡
    • 更少节点时(如 3 主),每节点 ~5461 槽 → 依然均衡
  3. 带宽 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(槽正式归属新节点)

三、图解全景

迁移命令:

bash
redis-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:3

4.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 中的角色
  • ← 返回 **高可用与集群索引**

Knowledge4J — Java 知识库