Redis Cluster 里多 key 命令(MSET、事务、Lua)的前提是所有 key 在同一个槽里。Hash tag 是让它们「被迫」同槽的办法:只对 key 里花括号中的部分算 CRC16。这篇把 tag 的解析规则逐条对照服务器验证,再看它最典型的代价——热点:一个 tag 就是一个槽,一个槽只能在一个节点上。
user1:profile → 槽 12715
user1:orders → 槽 1543
user1:cart → 槽 9467
{user1}:profile → 槽 8106
{user1}:orders → 槽 8106
{user1}:cart → 槽 8106没有 tag 时同一个用户的三个 key 落在三个不同的槽(12715、1543、9467);加上 {user1} 之后,三个 key 都只用 user1 计算,槽位一致(8106)。
key 服务器槽位 参与计算的部分 推断槽位 一致
'a{b}c' 3300 'b' 3300 ✓
'{b}' 3300 'b' 3300 ✓
'{}a' 10875 '{}a' 10875 ✓
'{a' 10276 '{a' 10276 ✓
'a}' 5921 'a}' 5921 ✓
'{{a}}' 10276 '{a' 10276 ✓
'{a}{b}' 15495 'a' 15495 ✓
'{a}}' 15495 'a' 15495 ✓
'x{}{y}' 14166 'x{}{y}' 14166 ✓
'a{b' 13340 'a{b' 13340 ✓
'{a}b}c' 15495 'a' 15495 ✓
'' 0 '' 0 ✓我把「取第一个 { 和它之后第一个 } 之间的内容,内容为空则整个 key 参与计算」这条规则实现出来,对 12 个边界写法逐条与 CLUSTER KEYSLOT 对照,全部一致。几个容易搞错的:{}a(空花括号)整个 key 参与;{{a}} 取的是 {a;{a}{b} 只看第一组;缺右括号的 {a 整个 key 参与。空字符串的槽位是 0。
各主库 DBSIZE: {7360: 0, 7361: 100000, 7362: 0} CLUSTER COUNTKEYSINSLOT 8106 = 100000 (向该槽的主库查询)10 万个 key 都用 {user1},它们全在槽 8106,也就全在同一个主库(三个主库 DBSIZE 为 0 / 100000 / 0)。槽是迁移的最小单位,这个热点槽没法被拆分到别的节点——只能把整个槽搬走。
各主库 DBSIZE: {7360: 28124, 7361: 35937, 7362: 35939}
64 个 tag 落在 64 个不同的槽把 tag 拆成 64 份({user1:0}…{user1:63}):10 万个 key 分散到 64 个不同的槽,三个主库 28124 / 35937 / 35939,不是严格均等(64 个 tag 掉进三个槽段是随机的)。代价是:只有落在同一个 tag 里的 key 才能一起做多 key 操作。所以 tag 的粒度就是「你需要多 key 原子性的最大范围」。