Redis Cluster通过哈希标签{}确保多key操作落在同一槽:仅{}内字符串参与CRC16计算,且所有KEYS的{}内容必须完全一致,否则触发CROSSSLOT错误;该机制是集群硬限制下的唯一可行解法。

必须用 {} 把所有 KEYS 中参与哈希计算的部分统一包起来,且所有 KEYS 的 {} 内容完全一致,否则 EVAL 或 EVALSHA 一定会报 CROSSSLOT 错误——这不是脚本问题,是集群硬限制。
为什么 {} 是唯一可行的解法
Redis Cluster 计算 slot 时只取第一个成对 {} 里的字符串做 CRC16,其余部分被忽略。比如 {user:1001}:profile 和 {user:1001}:settings 都只对 user:1001 哈希,必然落到同一 slot;而 user:1001:profile 和 order:1001:items 整个 key 参与哈希,几乎不可能同 slot。
- 不合法写法:
user{1001}:profile({没闭合,整个 key 参与哈希) - 语义模糊但等效写法:
user:{1001}:settings和{1001}:stats(都哈希1001,但可读性差) - 嵌套
{}只认最内层第一对:user:{abc{def}:xyz实际取def算 slot -
{}不能为空:user:{}:profile会被当成整个 key 处理,失去控制力
EVAL 调用时 KEYS 参数必须显式传入带相同 tag 的 key
脚本里硬编码的 key(如 'user:1001:a')不会被校验,运行时报 ERR Lua script attempted to access a non-local key;只有出现在 KEYS 数组里的 key 才会被 Redis 检查 slot。
- ✅ 正确:
EVAL "return redis.call('GET', KEYS[1])" 2 {user:1001}:a {user:1001}:b - ❌ 错误:
EVAL "return redis.call('GET', 'user:1001:a')" 0(无KEYS,集群禁止) - ❌ 错误:
EVAL "return redis.call('GET', KEYS[1] .. ':counter')" 1 {user:1001}:a(脚本内拼接的新 key 不在KEYS中) - 客户端如 Lettuce/Jedis 会先调
JedisClusterCRC16.getSlot()校验每个KEYS,不一致直接抛JedisClusterException
哪些场景下 {} 也救不了你
{} 只解决 key 分配问题,不改变命令本身的能力边界。
-
INCR、LPOP、LPUSH等单 key 命令,加了{}也不能变成多 key 操作 -
SCAN、KEYS在集群模式下被完全禁用,{}不可绕过 - 业务上必须读写多个无关用户的数据(如后台批量统计),别强撑 Lua 原子性——改用多次单 key 请求 + 客户端逻辑补偿
- 上线前必须用
redis-cli --cluster check看 slot 分布,确认打上相同 tag 的 key 真的落在了同一个 slot;光靠命名规则不验证,扩容或 rehash 后可能突然出问题
最容易被忽略的是:slot 分布不是静态的。哪怕你今天用 {user:1001} 全部对齐,一次 redis-cli --cluster rebalance 或节点增减后,CLUSTER KEYSLOT {user:1001}:a 和 CLUSTER KEYSLOT {user:1001}:b 的结果可能就不同了——所以必须定期验证,不能只信命名约定。


















