keys和argv是redis服务端硬性隔离的两类参数:keys决定脚本能否执行(集群校验、本地化、原子性),argv仅为纯字符串数据容器;混用会导致crossslot、noauth等错误或静默失败。

KEYS 和 ARGV 在 Redis Lua 脚本里不是“风格偏好”问题,而是服务端硬性隔离的两类参数:KEYS 决定脚本能否执行(集群校验、本地化、事务原子性),ARGV 只是数据容器,混用会直接触发报错或行为错乱。
KEYS[1] 不能替换成 ARGV[1],哪怕值一样
Redis 明确禁止在 redis.call() 的 key 参数位置使用 ARGV[n]。例如 redis.call("GET", ARGV[1]) 即使 ARGV[1] 的值是 "mykey",也会报 ERR bad lua script。
- 根本原因:Redis 在执行前就扫描脚本中所有
KEYS[n]引用,提取出实际要操作的 key 列表,用于集群 slot 校验、本地执行判定、ACL 权限检查 -
ARGV不参与任何服务端预检,它只是纯字符串数组,进不了 key 路由逻辑 - 常见翻车:想“复用脚本”,把 key 名也塞进
ARGV,结果脚本在集群里直接报CROSSSLOT或NOAUTH
EVAL 命令里 key 个数写错,整个 KEYS/ARGV 映射就全偏移
EVAL 命令结构是 EVAL <script> <numkeys> <key1> <key2> ... <arg1> <arg2> ...</script>,其中 numkeys 是整数,决定了前几个参数进 KEYS,剩下的全进 ARGV。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 写成
EVAL "return KEYS[1]" 0 mykey→mykey进了ARGV[1],KEYS是空表,脚本取KEYS[1]就报index out of bounds - 写成
EVAL "return KEYS[1]" 2 mykey hello→KEYS[1] == "mykey",KEYS[2] == "hello",但hello根本不是 key,却参与了 slot 计算,可能引发CROSSSLOT - 调试建议:脚本开头加
return {#KEYS, #ARGV, KEYS, ARGV}快速确认映射是否符合预期
集群模式下拼接新 key 一定崩,因为只校验 KEYS 不校验运行时构造
脚本里写 local newkey = KEYS[1] .. ":counter" 看似合理,但 newkey 不在原始 KEYS 数组里,Redis 完全不感知它的 slot 分布。
- 如果
newkey和KEYS[1]不同槽,执行redis.call("INCR", newkey)会报ERR Lua script attempted to access a non-local key - 正确做法:所有要操作的 key,必须显式传入
EVAL的 key 参数列表,哪怕只是临时 key;不要靠字符串拼接生成 key - 唯一安全的同槽方案:用哈希标签,比如
user:{1001}:profile和user:{1001}:counter,大括号内内容一致,CRC16 结果就相同
ARGV 里传数字或布尔,不转型就用会静默失败
Redis 只往 Lua 里传字符串,ARGV[1] 永远是 string 类型,哪怕你从客户端传的是整数 123 或布尔 true。
-
if ARGV[1] > 10 then ... end→ 永远为 false,因为字符串比较和数值比较规则不同 - 必须显式转:用
tonumber(ARGV[1]),但它可能返回nil(比如传了空字符串或非数字),后续直接参与运算会触发attempt to perform arithmetic on a nil value - 空值判断要双重:既查
ARGV[1] ~= nil,也查ARGV[1] ~= "",因为不同客户端对空参数处理不一致
最易被忽略的一点:KEYS 和 ARGV 的边界不是脚本写的,而是 EVAL 命令里那个数字决定的——少写一个 numkeys,后面全乱;多写一个,就把不该当 key 的值拉进了 slot 校验,集群直接拒掉。这个数字必须和你脚本里实际引用的 KEYS[n] 最大下标严格一致。










