Redis Hash Tag:解决哈希槽分片下的键聚合问题
Hash Tag(哈希标签),它是 Redis 集群分片(基于哈希槽)场景下的核心技巧,用于实现相关键的聚合存储,避免分布式环境下的跨槽操作问题。
一、先铺垫:为什么需要 Hash Tag?
在 Redis 集群模式下,核心分片规则是:
- Redis 集群预设
16384个哈希槽(Hash Slot); - 对每个键(Key)通过
CRC16(key) % 16384计算得到对应的哈希槽; - 每个集群节点负责一部分哈希槽,键最终存储在其哈希槽对应的节点上。
这种默认规则会带来一个问题:相关联的键(比如 user:1001:name、user:1001:age、user:1001:score)可能被散列到不同的集群节点上。
此时如果执行跨键操作(比如 MGET user:1001:name user:1001:age、事务、Lua 脚本),会出现「跨槽操作不支持」的错误,或者需要跨节点通信,大幅降低性能。
而 Hash Tag 的核心作用就是:强制让一组相关的键,计算出相同的哈希槽,从而存储在同一个 Redis 节点上,支持高效的跨键操作。
二、Hash Tag 核心工作原理
1. 语法规则
Hash Tag 利用大括号 {} 包裹的子字符串作为哈希计算的依据,Redis 集群在计算哈希槽时,会遵循以下优先级:
- 若键中包含一对完整的大括号
{},且大括号内有非空字符串(即{xxx},xxx不为空),则仅对大括号内的子字符串(Tag 内容)进行CRC16计算,忽略键的其他部分; - 若键中没有大括号、大括号为空(
{}),或大括号不完整(比如只有{没有}),则按默认规则对整个键进行CRC16计算。
2. 核心示例(直观理解)
下面一组键都包含 {user:1001} 作为 Hash Tag,它们会被计算到同一个哈希槽:
| 键名 | 参与哈希计算的内容 | 结果 |
|------|--------------------|------|
| user:1001:name(无 Tag) | 整个键 user:1001:name | 可能散列到槽 A |
| {user:1001}:name(有 Tag) | 仅 user:1001 | 散列到槽 B |
| {user:1001}:age(有 Tag) | 仅 user:1001 | 散列到槽 B(与上一个键同槽) |
| {user:1001}:score(有 Tag) | 仅 user:1001 | 散列到槽 B(与前两个键同槽) |
再看几个特殊情况(验证语法规则):
- 键
user:{1001}:name:参与计算的是1001,同组键order:{1001}:total也会和它同槽; - 键
{user}:1001:name:参与计算的是user,所有包含{user}的键都会同槽; - 键
user:1001{}:name:大括号内为空,按整个键计算; - 键
user:1001:{name}:参与计算的是name,仅和其他包含{name}的键同槽。
3. 底层计算流程
Redis 集群对键的哈希槽计算流程可简化为:
输入键 Key → 检查是否包含有效 Hash Tag({非空内容})→
是 → 提取 Tag 内容 → CRC16(Tag) % 16384 → 得到哈希槽;
否 → CRC16(Key) % 16384 → 得到哈希槽。
三、实操用法:如何正确使用 Hash Tag?
1. 基础用法:聚合相关键
需求:将用户 1001 的所有相关信息(姓名、年龄、分数)聚合到同一个节点,支持 MGET 批量查询。
# 1. 写入带 Hash Tag 的键
SET {user:1001}:name "Zhang San"
SET {user:1001}:age 25
SET {user:1001}:score 98
# 2. 批量查询(同槽操作,支持集群模式)
MGET {user:1001}:name {user:1001}:age {user:1001}:score
# 3. 事务操作(同槽操作,支持集群模式)
MULTI
SET {user:1001}:age 26
INCR {user:1001}:score
EXEC
2. 进阶用法:按业务维度聚合
需求:将同一个订单的所有相关数据(订单信息、商品列表、支付记录)聚合,方便后续原子操作。
# 按订单 ID 作为 Hash Tag,聚合订单相关键
HSET {order:20251229001}:info order_no "20251229001" amount 99.9
LPUSH {order:20251229001}:goods "商品A" "商品B"
SET {order:20251229001}:pay_status "paid"
# 批量获取订单相关数据(集群模式下无跨槽错误)
HMGET {order:20251229001}:info order_no amount
LRANGE {order:20251229001}:goods 0 -1
GET {order:20251229001}:pay_status
四、Hash Tag 的适用场景
1. 批量操作(MGET/MSET/HMGET 等)
这是最常见的场景,当需要对一组相关键执行批量读写时,用 Hash Tag 确保它们同槽,避免集群模式下的「CROSSSLOT Keys in different slots」错误。
2. 事务(MULTI/EXEC)与 Lua 脚本
Redis 事务和 Lua 脚本要求所有操作的键必须在同一个节点(同槽),否则无法保证原子性,Hash Tag 是实现分布式环境下原子事务的核心手段。
3. 数据分片聚合(按业务维度)
按用户、订单、店铺等业务维度聚合数据,方便后续的业务查询、数据迁移和运维管理,避免相关数据分散在多个节点。
4. 避免热点键分散(补充场景)
对于部分高频访问的相关键,通过 Hash Tag 聚合到同一个节点,便于针对性优化(如给该节点分配更多资源),同时避免热点键分散导致的负载不均衡。
五、注意事项与最佳实践
1. 避免「过度聚合」导致热点槽
这是使用 Hash Tag 最容易踩的坑:
- 若选择的 Tag 粒度太粗(比如
{user}),所有用户的相关键都会聚合到同一个哈希槽,导致该槽所在节点成为「热点节点」,承受所有用户的访问压力,违背 Redis 集群分片的负载均衡初衷; - 最佳实践:选择细粒度、高基数的 Tag 内容(如用户 ID、订单 ID),确保 Tag 的分布足够分散,使哈希槽均匀分布在各个集群节点上。
2. 统一 Tag 命名规范
团队内部需约定统一的 Tag 格式,避免因 Tag 写法不一致导致聚合失败,示例规范:
- 格式:
{业务类型:唯一ID}:属性,如{user:1001}:name、{order:20251229001}:info; - 禁止使用空 Tag(
{})、不完整 Tag(如user:1001:{); - 禁止在一个键中使用多个完整大括号(如
{user:1001}:{name},Redis 仅识别第一个完整的{}内的内容)。
3. 注意键的长度与 Tag 性能
Hash Tag 仅影响哈希槽的计算过程,对键的读写性能无直接影响,但需注意:
- 避免过长的 Tag 内容,增加
CRC16计算开销(虽影响极小,但需规范); - 键名整体不宜过长,否则会增加网络传输开销和内存占用。
4. 迁移数据时的注意事项
当 Redis 集群需要迁移哈希槽时,聚合在同一个槽的所有键会被一起迁移,相比分散的键,迁移效率更高,但需注意:
- 迁移期间,该槽的相关操作会被阻塞(短时间),需避开业务高峰;
- 若聚合的键数据量过大,可提前拆分 Tag 粒度,减少单次迁移的数据量。
5. 验证 Tag 效果的方法
可通过 Redis 提供的 CLUSTER KEYSLOT 命令,验证一组键是否在同一个哈希槽:
# 验证三个键是否同槽
CLUSTER KEYSLOT {user:1001}:name
CLUSTER KEYSLOT {user:1001}:age
CLUSTER KEYSLOT {user:1001}:score
# 输出结果(三个命令返回相同的数字,即同槽)
12345
12345
12345
六、总结
- Redis Hash Tag 的核心价值是强制相关键同槽存储,解决集群模式下跨槽操作的限制和性能问题;
- 核心语法是
{非空内容},Redis 仅对大括号内的内容计算哈希槽,其余部分不参与; - 适用场景包括批量操作、事务/Lua 脚本、业务维度数据聚合,核心是「聚合相关键,不破坏负载均衡」;
- 最大坑点是过度聚合导致热点槽,最佳实践是选择细粒度、高基数的 Tag 内容,同时统一团队命名规范。
Hash Tag 是 Redis 集群运维的必备技巧,合理使用能在保证分布式扩展性的同时,兼顾业务操作的原子性和高效性。
注意:本文归作者所有,未经作者允许,不得转载