根本原因是redis集群强制要求lua脚本所有keys必须落在同一slot,否则服务端入口校验即报crossslot或非本地key错误;唯一可靠解法是用{}哈希标签使keys中第一个{}内内容一致,如{user:1001}:profile和{user:1001}:settings,确保crc16计算结果相同。

Redis集群中Lua脚本报CROSSSLOT或ERR Lua script attempted to access a non-local key,根本原因不是脚本写得不对,而是传入的KEYS数组里多个key没落在同一个slot——这是集群硬性限制,绕不过,必须从key设计入手。
为什么EVAL和EVALSHA都报同slot错误
Redis集群把16384个slot分给不同节点,每个key通过CRC16算法算出归属slot。Lua脚本执行前,服务端会检查KEYS数组里所有key的slot是否一致,不一致直接拒绝,连脚本解析都不做。单机环境测通、上集群就崩,就是因为本地没slot概念。
-
EVALSHA只是发sha1值,校验逻辑和EVAL完全一样,不是“绕过”方案 - 客户端如Jedis/Lettuce会在调用前用
JedisClusterCRC16.getSlot()逐个检查KEYS,不一致就抛JedisClusterException - 错误信息典型长这样:
(error) CROSSSLOT Keys in request don't hash to the same slot或ERR 'EVAL' command keys must in same slot
用{}做Hash Tag是唯一可靠解法
Redis只取第一个成对{}里的内容做哈希计算,其余部分被忽略。比如{user:1001}:profile和{user:1001}:settings都只对user:1001哈希,必然同slot。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 正确写法:
channel_health:{5}、channel_blocked:{5}→ 都哈希5 - 错误写法:
user:1001:profile、order:1001:items→ 整个字符串参与哈希,基本不同slot - 嵌套
{}只认最内层第一对:user:{abc{def}:xyz实际取def算slot -
{}里不能为空:{}会被当成整个key处理,失去控制力
脚本里KEYS和ARGV怎么传才合法
脚本本身不决定slot分布,真正起作用的是你传进去的KEYS数组——它们必须已满足同slot条件。阿里云等托管集群还会额外校验:所有key必须显式出现在KEYS数组里,不能赋值给Lua变量后再用。
- ❌ 错误:
local key = KEYS[1]; redis.call("INCR", key)→ 变量替换违反校验 - ✅ 正确:
redis.call("INCR", KEYS[1])→ key位置必须是KEYS数组索引 - 服务端生成key时就该带tag,别等Lua里拼接——脚本里不能动态构造新key并保证同slot
- 即使脚本里硬编码
"user:1001",只要没出现在KEYS里,就是“非本地key”,直接拒绝
上线前必须验证slot分布,别信命名规则
光靠加{}不验证,扩容或rehash后可能突然出问题。必须用redis-cli --cluster check或cluster keyslot {key}确认打上相同tag的key真落在同一个slot。
最容易被忽略的一点:Hash Tag只管key分配,不管命令是否支持多key。INCR、LPOP这类单key命令加了{}也没用;SCAN、KEYS在集群下被禁用,Hash Tag不可绕过。如果业务必须读写多个无关用户数据,就别强撑Lua原子性——改用多次单key请求+客户端逻辑补偿。










