redis集群在eval入口阶段即校验keys数组中所有key的slot是否一致,不一致则直接返回crossslot错误,不解析lua脚本;哈希标签{}仅取第一个成对花括号内内容参与crc16计算,动态拼接或argv传key均无效,必须显式列入keys。

为什么EVAL一执行就报CROSSSLOT,连脚本内容都不看?
因为Redis集群在入口阶段就做slot校验,只要KEYS数组里任意两个key算出的slot不一致,立刻拒绝执行,根本不会进入Lua解析或redis.call调用流程。这不是客户端没重定向,是服务端硬拦截。
典型错误信息就是:CROSSSLOT Keys in request don't hash to the same slot 或 ERR 'EVAL' command keys must in same slot。
- 单机Redis完全不检查这个,所以本地调试通过、上集群就崩,特别容易漏
-
EVALSHA和EVAL校验逻辑完全一致,只是少传脚本体,不能绕过slot限制 - 客户端如
Jedis、go-redis会在调用前主动计算每个KEYS[i]的slot,不一致直接抛异常,比如JedisClusterException
{}哈希标签怎么写才真正生效?
Redis只取第一个成对{}里的内容参与CRC16哈希,其余全部忽略——这是唯一被官方支持的同slot方案,但写错括号就白搭。
- ✅ 正确:
user:{1001}:profile、order:{1001}:items→ 都哈希1001,必然同slot - ❌ 错误:
user:1001:profile→ 整个字符串哈希,和order:1001:items基本不同slot - ⚠️ 危险:
user{1001}:profile(没闭合)、user:{abc{def}:xyz(取最内层{def},但语义难维护) - 空
{}或user:{}:profile会被当成全长哈希,失去控制力
脚本里拼接key、用ARGV代替KEYS行不行?
不行。所有要被redis.call()访问的key,必须显式出现在KEYS参数里;否则运行时会报ERR Lua script attempted to access a non-local key。
- ❌
redis.call('GET', 'user:1001:counter')—— 字符串字面量,不在KEYS中,集群模式下禁止 - ❌
redis.call('GET', KEYS[1] .. ':counter')—— 动态拼接,不参与slot校验,运行时报错 - ✅ 必须把
{1001}:counter也放进KEYS数组,比如EVAL "... " 3 {1001}:a {1001}:b {1001}:counter - 用
ARGV传key名是常见误区:它不参与slot校验,但redis.call()访问时仍属“非本地key”
哪些场景Hash Tag也救不了你?
Hash Tag只解决key分片问题,不改变命令本身的约束。有些操作天生就不适合集群多key语义。
-
INCR、LPOP、LPUSH等单key命令,加{}没意义——它们本来就不接受多个key -
SCAN、KEYS在集群中被完全禁用,Hash Tag无法绕过 - 后台批量统计多个无关用户数据,别硬套Lua原子性;该用多次单key请求+客户端补偿逻辑就用
- 上线前必须跑
redis-cli --cluster check验证tagged key是否真落在同一slot——扩容或rehash后可能突然失效











