redis cluster 中 eval 报 crossslot 错误是因为脚本中所有 key 必须落在同一哈希槽,而 cluster 仅校验 keys 数组中的 key,要求其哈希标签一致(如 user:{1001}:a 和 user:{1001}:b),动态拼接的 key 不参与预检,运行时可能触发 moved。

为什么 EVAL 在 Redis Cluster 中会报 CROSSSLOT 错误
Redis Cluster 要求同一个 Lua 脚本里所有 key 必须落在同一个哈希槽(hash slot)上,否则执行 EVAL 时直接返回 CROSSSLOT Keys in request don't hash to the same slot。这不是 Lua 本身的问题,而是 Cluster 架构的硬性约束:脚本必须路由到唯一节点执行,不能跨节点协调。
常见触发场景包括:
- 脚本中硬编码多个不同 key,比如
GET user:1001和GET order:2002(前缀不同,大概率不在同槽) - 用
KEYS[1]和KEYS[2]传入两个不相关的 key - 在脚本里拼接 key(如
"user:"..ARGV[1]),但没确保所有生成 key 都属于同一槽
如何让 key 落在同一 slot:用 {...} 手动控制哈希标签
Redis Cluster 计算 slot 时,只取 key 中第一个 { 和其后第一个 } 之间的字符串做 CRC16。只要多个 key 的花括号内部分一致,它们就必然落在同一 slot。
实操建议:
- 设计 key 时主动嵌入统一标签,例如
user:{1001}:profile和user:{1001}:settings—— 两者都按"1001"计算 slot - 避免
user:{1001}:profile和order:{1001}:items混用 —— 标签不同("1001"vs"1001"看似相同,但实际是两个独立标签;不过这里其实相同,真正危险的是user:{1001}和order:{2002}) - 不要依赖业务 ID 自身“看起来一样”:
user:1001和cart:1001完全无关,slot 必然不同
EVALSHA 能绕过 slot 检查吗?不能,校验逻辑完全一致
EVALSHA 只是复用已加载脚本的 SHA1 值,不改变 key 路由规则。Cluster 仍会在执行前解析脚本中的 KEYS 数组,并对每个 key 做 slot 校验。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
关键点:
- 即使脚本内容不变,只要传入的
KEYS参数跨槽,EVALSHA一样报CROSSSLOT -
SCRIPT LOAD本身不校验 slot,它只是存脚本;问题一定出在EVAL/EVALSHA调用时 - 某些客户端(如 Jedis、redis-py)在 Cluster 模式下会自动尝试
EVALSHA,但失败后回退EVAL,这不会规避 slot 限制
真正安全的 Cluster Lua 脚本写法
核心原则:把 slot 绑定责任交给调用方,而不是脚本内部动态计算。
正确做法:
- 脚本只操作
KEYS[1]、KEYS[2]…,且文档明确要求这些 key 必须属于同一逻辑实体(如“同一用户的所有数据”) - 调用时传入的 key 必须带一致的哈希标签,例如:
EVAL "return {KEYS[1], KEYS[2]}" 2 user:{1001}:a user:{1001}:b - 避免在 Lua 里用
redis.call("GET", "user:"..ARGV[1])这类硬编码拼接 —— 这种 key 不在KEYS数组里,Cluster 无法预检,运行时可能报MOVED或ASK错误 - 如果必须动态构造 key,确保构造逻辑和哈希标签强绑定,例如
"user:{"..ARGV[1].."}:cache",并要求ARGV[1]是稳定标签值
最易被忽略的一点:Cluster 不检查 Lua 脚本体内的字符串拼接结果是否合法,只检查显式传入 KEYS 数组的那些 key。所以看似“绕过”的写法,往往在运行时才暴露问题。










