Redis Lua 脚本怎样通过 KEYS 和 ARGV 区分参数
我在排查一段 Redis Lua 脚本时,最先怀疑的是脚本逻辑,最后发现问题出在调用参数的分界:业务值被放进了 KEYS,真正的 key 却没有按 numkeys 传入。结果不是简单的“取不到值”,而是脚本在单机环境里看起来能跑,换到集群或换一组参数后就变得难以判断。
记住一个规则就能拆开这类问题:KEYS 只放 Redis key 的名字,ARGV 放阈值、状态、新值等普通参数;两者的边界由 EVAL 的 numkeys 决定。
官方地址:https://redis.io/docs/latest/develop/programmability/eval-intro/
先看 EVAL 的参数边界
EVAL 的基本形式是 EVAL script numkeys [key ...] [arg ...]。脚本字符串之后的第一个数字不是“参数总数”,而是后面有多少个参数属于 key。Redis 从第三个参数开始读取 key 名称,读完 numkeys 个之后,剩下的才会进入普通参数区域。
# numkeys=1:user:1001 属于 KEYS[1],42 属于 ARGV[1]
redis-cli EVAL "return {KEYS[1], ARGV[1]}" 1 user:1001 42
# numkeys=0:没有 key,后面的文本全部是普通参数
redis-cli EVAL "return ARGV[1]" 0 hello
因此,不能把 numkeys 写成“后面一共有几个值”。如果写成 2,Redis 会把接下来的两个参数都放入 KEYS,即使第二个参数本来只是业务状态。

在 Lua 中分别读取 KEYS 和 ARGV
Redis 会在脚本执行上下文中预先提供两个表。KEYS[1] 到 KEYS[n] 对应显式传入的 key,ARGV[1] 到 ARGV[m] 对应剩余的普通参数。下标从 1 开始,不是 Go、JavaScript 那种从 0 开始。
-- KEYS[1] 是要读取的 key,ARGV[1] 是业务传入的最低分数
local current = redis.call('GET', KEYS[1])
local minimum = tonumber(ARGV[1])
-- key 不存在时返回明确结果,避免把 nil 当成普通数字比较
if not current then
return {0, 'missing'}
end
-- 只把业务参数用于判断,不把它拼进 key 名称
if tonumber(current) >= minimum then
return {1, current}
end
return {0, current}
这段代码里的职责是分开的:KEYS[1] 表示数据位置,ARGV[1] 表示判断条件。普通参数不要拼接成 key 再访问,否则既破坏了参数约定,也会让 Redis Cluster 无法提前知道脚本可能访问哪些 key。
用 redis-cli --eval 对照调用顺序
如果脚本放在 check-score.lua 中,redis-cli --eval 使用逗号分隔 key 区域和普通参数区域。逗号前的参数会进入 KEYS,逗号后的参数会进入 ARGV,这和 EVAL 的 numkeys 规则是同一件事的命令行写法。
# 第一个参数是 KEYS[1];逗号后才是 ARGV[1] redis-cli --eval check-score.lua account:1001 , 60 # 两个 key 要全部放在逗号前,普通阈值仍放在逗号后 redis-cli --eval compare.lua account:1001 account:1002 , 10
排查时可以先让脚本临时返回参数内容,确认边界后再换回实际逻辑。不要把日志、调试字符串或普通状态值放在 key 区域来“凑数量”,那会把错误隐藏到调用端。
在 Go 客户端固定参数职责
客户端封装最好把 key 和普通参数拆成两个明确的切片。以 go-redis 风格为例,Keys 切片会对应 KEYS,Args 切片会对应 ARGV;具体方法签名请按项目依赖版本调整。
// EvalScore 把 key 与普通阈值分开传给 Redis Lua 脚本。
func EvalScore(ctx context.Context, rdb *redis.Client, key string, minimum int64) (string, error) {
const script = `
-- KEYS[1] 只表示数据 key,ARGV[1] 表示业务阈值
local value = redis.call('GET', KEYS[1])
if not value then
return 'missing'
end
if tonumber(value) >= tonumber(ARGV[1]) then
return value
end
return 'below'
`
// Keys 与 Args 分开,避免把 minimum 误当作 key。
result, err := rdb.Eval(ctx, script, []string{key}, minimum).Result()
if err != nil {
// 保留 Redis 返回的错误,让调用方决定重试或记录。
return "", err
}
return result.(string), nil
}
多人协作时,命名比位置更可靠:调用函数时先写 key,再写 minimum;脚本内部只从 KEYS 取数据位置,只从 ARGV 取业务输入。若脚本需要多个 key,就在同一份 key 列表中按固定顺序传入,并在注释中写清每个下标的含义。

集群场景要守住 key 的边界
Redis 官方文档要求脚本访问的所有 key 名称都作为输入 key 显式提供,脚本不要根据字符串拼接或数据内容临时生成 key。原因是集群需要根据 key 识别路由范围;把 key 藏在 ARGV 或脚本内部,会让调用约定和集群路由都变得不可靠。
| 输入内容 | 放置位置 | 脚本访问方式 | 典型例子 |
|---|---|---|---|
| Redis key 名称 | KEYS 区域 | KEYS[1]、KEYS[2] | user:1001 |
| 阈值、状态、新值 | ARGV 区域 | ARGV[1]、ARGV[2] | 60、enabled |
| 脚本源码 | EVAL 第一个参数 | Lua 程序 | return redis.call(...) |
最后可以按三点复查调用:第一,numkeys 是否等于 key 的数量;第二,脚本中的每个 KEYS[i] 是否都能在调用端找到对应位置;第三,业务值是否全部从 ARGV 读取。三点一致后,再考虑脚本本身的返回值和异常处理。
常见问题
KEYS 和 ARGV 的下标从 0 开始吗?
不开始。Redis Lua 脚本中的两个表都按 Lua 习惯从 1 开始访问,例如第一个 key 是 KEYS[1],第一个普通参数是 ARGV[1]。
没有 key 时应该怎样传参?
把 numkeys 设为 0,脚本不要访问 KEYS,所有后续输入都会进入 ARGV。命令行写法则是在 --eval 后直接写脚本,再写逗号和普通参数。
可以把 key 名称放进 ARGV 再拼接吗?
不建议。需要访问的 key 应在调用时显式放入 KEYS,并保持固定顺序;这不仅让脚本更容易读,也符合 Redis Cluster 对脚本 key 输入的要求。
总结
numkeys 是 EVAL 的分界线:它先决定有多少个输入属于 key,Redis 再把这些值放到 KEYS,把剩余值放到 ARGV。调用端把 key 和普通参数分成两组,Lua 端按 1 开始的下标读取,集群场景中不动态生成 key,Redis Lua 脚本的参数契约就会清晰很多。
Redis ZSET 怎样查询分数区间内的成员
- 上一篇
- Redis ZSET 怎样查询分数区间内的成员
- 下一篇
- Redis SCAN 为什么不能当作一次性全量遍历
-
- 数据库 · Redis | 1小时前 | Redis · Redis SCAN Redis游标遍历 SCAN全量查询 Redis键空间 COUNT参数
- Redis SCAN 为什么不能当作一次性全量遍历
- 236浏览 收藏
-
- 数据库 · Redis | 1小时前 |
- Redis ZSET 怎样查询分数区间内的成员
- 484浏览 收藏
-
- 数据库 · Redis | 1小时前 |
- Redis Hash 怎样一次读取多个字段并设置 TTL
- 104浏览 收藏
-
- 数据库 · Redis | 1小时前 |
- Redis Stream 消费组为什么重复收到消息
- 463浏览 收藏
-
- 数据库 · Redis | 1小时前 |
- Redis Stream XREAD COUNT 怎样限制单次读取消息数
- 166浏览 收藏
-
- 数据库 · Redis | 1小时前 |
- Redis Stream 怎样查看消费者组待处理消息
- 250浏览 收藏
-
- 数据库 · Redis | 1小时前 |
- Redis JSON.SET 路径不存在为什么没有创建对象
- 204浏览 收藏
-
- 数据库 · Redis | 1小时前 |
- Redis EXPIRE 怎样只在新 TTL 更短时更新
- 244浏览 收藏
-
- 数据库 · Redis | 1小时前 |
- Redis JSON.SET 怎样只在键存在时更新
- 173浏览 收藏
-
- 数据库 · Redis | 1小时前 |
- Redis CLIENT PAUSE WRITE 的主节点切换步骤与写入保护边界
- 458浏览 收藏
-
- 数据库 · Redis | 1小时前 |
- Redis GETDEL 怎样读取后删除键
- 306浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 603次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 643次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 673次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 639次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 460次使用
-
- Redis Stream XTRIM 如何避免消费组积压无限增长
- 2026-09-12 501浏览
-
- Redis AOF rewrite 期间如何判断磁盘与内存压力
- 2026-09-12 501浏览
-
- Redis RDB 和 AOF 怎么按可接受数据丢失量选择
- 2026-09-08 501浏览
-
- Redis XAUTOCLAIM 之后为什么仍有 pending:JUSTID、PEL 与消息删除边界
- 2026-08-29 501浏览
-
- Redis SET 的 GET 与 KEEPTTL 怎么一起验收:旧值返回、续期与回滚边界
- 2026-08-20 501浏览

