集群不加{}必报CROSSSLOT错误,因Redis集群要求EVAL/EVALSHA中所有KEYS必须同slot,而slot由CRC16(key)%16384计算;未加{}时整个key参与哈希,user:1001:balance与user:1001:score几乎必然不同slot。

为什么集群里不加{}就一定会报CROSSSLOT错误
因为Redis集群强制要求:所有出现在EVAL或EVALSHA的KEYS参数里的key,必须落在同一个slot。而slot由CRC16(key) % 16384决定——没加{}时,整个key字符串参与计算,user:1001:balance和user:1001:score算出来的slot几乎必然不同。
错误信息典型长这样:(error) CROSSSLOT Keys in request don't hash to the same slot 或 ERR Lua script attempted to access a non-local key。这不是Lua语法错,也不是客户端bug,是集群节点在收到请求时、甚至还没解析脚本内容前就直接拒绝了。
- 单机Redis完全不检查slot,所以本地调试永远成功,一上集群就崩
-
EVALSHA不是绕过手段:它只传sha1值,但校验逻辑和EVAL完全一致,照样卡在同slot检查这一步 - Jedis/Lettuce等客户端会在调用前主动调
JedisClusterCRC16.getSlot()逐个算slot,不一致就提前抛JedisClusterException,不发请求
{}怎么让多个key落到同一slot:只取第一对{}内字符串哈希
Redis规定:key中第一个成对出现的{和}之间的内容,才是实际参与CRC16计算的部分,其余字符全被忽略。比如:
-
user:{1001}:balance→ 只对1001哈希 -
order:{1001}:items→ 同样只对1001哈希 -
cart:{abc{def}:pending→ 只取abc{def?不对,只认第一对,即abc{def里第一个{和它右边最近的},所以是def -
user:{}:profile→{}为空,整个key回退参与哈希,失去控制力
关键点:服务端生成key时就必须带tag,不能靠Lua脚本里拼接。脚本里写redis.call('GET', KEYS[1] .. ':counter'),这个新key不会被校验,运行时大概率触发MOVED或ASK重定向失败。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
KEYS数组必须显式传入所有要操作的key,不能靠脚本内硬编码
Lua脚本本身不决定slot分布,Redis只看你传进来的KEYS数组。哪怕脚本里写死redis.call('GET', 'user:1001:balance'),只要它没出现在KEYS里,就被视为“非本地key”,直接拒绝执行。
- 正确调用:
EVAL "return redis.call('HINCRBY', KEYS[1], 'money', -100)" 2 {user:1001}:balance {user:1001}:score - 错误调用:
EVAL "return redis.call('HINCRBY', 'user:1001:balance', 'money', -100)" 0——KEYS数量为0,集群模式下禁止 - 错误调用:
EVAL "return redis.call('HINCRBY', KEYS[1], 'money', -100)" 1 user:1001:balance—— 没加{},整个字符串哈希,无法保证同slot
所有要读写的key,必须全部显式列在KEYS参数里,且全部带相同Hash Tag。
Hash Tag救不了的场景,别硬扛
Hash Tag只解决key路由问题,不改变命令本身的语义限制:
-
INCR、LPOP、LPUSH等单key命令,加了{}也没法变多key操作 -
SCAN、KEYS在集群模式下被完全禁用,Hash Tag不可绕过 - 如果业务需要批量处理1000个用户无关数据(如后台统计),别强求Lua原子性——改用多次单key请求 + 客户端补偿逻辑更稳妥
- 上线前必须用
redis-cli --cluster check验证:打上相同tag的key是否真落在了同一个slot。扩容或rehash后,slot分布可能突变,光靠命名规则不验证,迟早出问题

















