Redis集群通过分片方式把数据分布到16384个槽位上,从而实现横向扩展。当业务需要把多个key放在同一个节点执行如MSET、MGET或者事务这类多key操作时,就可以利用hash tag机制,让这些key计算出相同的槽位。具体做法是在key中插入一对花括号,集群只对花括号内字符串做CRC16散列,而不是对整个key做散列。这个特性用好了能解决跨槽报错,用不好就会引发难以排查的集群倾斜。

hash tag的基本原理与计算过程
在Redis集群里,普通的key定位公式是slot = CRC16(key) % 16384。一旦key里出现了字符{,并且后面有对应的},集群会提取{和}之间的内容作为hash tag,用这部分内容代替整个key参与CRC16计算。例如user:{1001}:name和user:{1001}:age都会以1001作为散列输入,因此必然分配到同一个槽位,也必然在同一个节点上。这种规则在源码的clusterKeysSlot函数中清晰可见,属于集群协议层面的约定。
需要注意的是,如果花括号没有闭合,或者里面为空,集群会退化成对整个key做散列。比如key写成{user:1001或者user:{}:name,都不会触发hash tag效果。此外,如果同一个key里有多对花括号,只会取第一对中间的内容。理解这些边界条件,能帮我们在设计key格式时少踩坑,不至于以为加了括号就一定能同槽。
从底层看,CRC16算法本身会把任意长度的输入压缩成16位整数,再对16384取模。由于16384是2的14次方,实际只用了低14位。当我们人为把大量key的hash tag设成同一个值,这些key的低14位完全一致,也就全部挤进同一个槽。槽是集群调度的最小单位,一个槽只能由一个主节点负责,因此同tag的key越多,该节点压力越大。
// Java侧计算Redis集群槽位的简化逻辑
public class SlotUtil {
public static int getSlot(String key) {
int s = key.indexOf('{');
if (s != -1) {
int e = key.indexOf('}', s + 1);
if (e != -1 && e > s + 1) {
key = key.substring(s + 1, e); // 提取hash tag
}
}
// 省略CRC16具体实现,最终返回 crc16(key) % 16384
return crc16(key) % 16384;
}
private static int crc16(String key) {
// 标准CRC-16/XMODEM实现
return 0;
}
}
使用hash tag引发的数据倾斜与热点问题
最常见的误区是把用户唯一标识直接作为hash tag,比如order:{userId}:list。当某些大客户userId访问极高频时,持有该tag对应槽位的节点CPU和内存会远高于其他节点。集群虽然整体有十几个节点,但流量全打在一台上,扩展性名存实亡。更糟糕的是,如果该节点宕机,受影响的是这一批大客户的所有相关key,故障半径被放大。
另一个隐蔽问题是数据总量倾斜。假设平台做活动,给每个用户生成十万条以{userId}为tag的临时key,而少数刷子账号批量注册,就会让某几个槽位数据量突破单节点内存上限。Redis集群不支持槽跨节点拆分,只能迁移整个槽,一旦单槽过大,迁移过程会长时间阻塞并影响线上读写。
要避免上述情况,应当从业务维度收敛hash tag的取值基数。比如按城市编码或机房编号做粗粒度tag,而不是细到每个用户。如果必须用户级原子操作,可以引入本地缓存合并写,或把多key操作拆成单key加应用层补偿。下面这个表格对比了不同tag策略的影响:
| tag策略 | 同槽key数量 | 倾斜风险 | 多key操作便利性 |
|---|---|---|---|
| 每个用户ID | 极高 | 高 | 最好 |
| 用户所属城市 | 中等 | 中 | 较好 |
| 固定业务名 | 低 | 低 | 差 |
正确使用hash tag的实践建议与监控手段
在方案设计阶段,开发团队应先列出所有需要多key原子操作的场景,确认是否真的必须同槽。有些场景可以用Redis的Hash结构把多个字段合并成一个key,从而不需要hash tag也能一次操作。例如把用户画像放进一个hset而不是十个独立string,用hmget读取,既规避跨槽又减少key数量。
如果确实要用hash tag,建议在配置中心维护tag前缀白名单,禁止随意拼接用户变量。同时在上线前用脚本模拟key分布,调用cluster keyslot命令批量验证槽位集中度。如下命令可以快速检查某类key落在哪个槽:
# 检查带tag的key对应的槽位
redis-cli cluster keyslot "user:{1001}:name"
redis-cli cluster keyslot "user:{1001}:age"
# 两者返回结果应完全一致
运行时监控也不可缺少。通过redis-cli --cluster check或者监控系统的槽分布面板,观察各节点内存和命令数差异。一旦发现某个节点槽位数据量超过均值两倍,就要回溯是不是hash tag设置不当。必要时用cluster reshard把热点槽拆分到不同节点,但这只是临时止血,根本解决还是要调整key设计。只有把原理、设计、监控三者结合,hash tag才会成为利器而非隐患。