在 Redis Lua 脚本中,正确使用 KEYS
和 ARGV
对脚本的正确性 、性能 以及在集群模式下的兼容性至关重要。下面是一个快速对比表格,帮你掌握核心原则:
方面 | KEYS (用于键) |
ARGV (用于参数) |
---|---|---|
用途 | 传递所有要操作的 Redis 键名 | 传递脚本所需的其他参数(如值、标志、配置数值) |
访问方式 | KEYS[1] , KEYS[2] , ... (索引从 1 开始) |
ARGV[1] , ARGV[2] , ... (索引从 1 开始) |
数量声明 | 必须在 EVAL/EVALSHA 命令开始时用 numkeys 明确声明 |
无需单独声明数量 |
集群模式要求 | 所有键必须位于同一个哈希槽,否则脚本会执行失败 | 与数据分片无关,可任意传递 |
最佳实践 | 1. 必须 通过 KEYS 数组传递,禁止硬编码键名 2. 键名尽量简洁,减少内存占用 3. 在集群中确保使用哈希标签({} )保证键在同一槽位 |
1. 所有非键参数都应通过 ARGV 传递 2. 数值参数需用 tonumber() 转换 3. 字符串参数无需转换 |
📌 1. 基本规范与使用方法
**KEYS
数组用于明确指定脚本要操作的所有 Redis 键。这在 Redis 集群模式下是 必须的**,因为集群需要根据键来将请求路由到正确的分片。在 EVAL
或 EVALSHA
命令中,你需要通过 numkeys
参数指明 KEYS
数组的数量。
**ARGV
** 数组用于传递所有非键参数,例如操作的数值、标志位、配置值等。这些参数与数据分片无关。
一个简单的示例,演示如何传递参数:
css
EVAL "return {KEYS[1], KEYS[2], ARGV[1], ARGV[2]}" 2 key1 key2 arg1 arg2
此命令会返回 ["key1", "key2", "arg1", "arg2"]
,清晰地展示了参数是如何对应的。
⚠️ 2. 集群模式下的特殊要求
在 Redis 集群环境 中,一个 Lua 脚本中操作的所有键(即 KEYS
数组中的所有键)必须位于同一个哈希槽 (hash slot) 中。如果这些键分布在不同的槽,命令会报错 -ERR 'xxx' command keys must in same slot
。
解决方案 :对于需要在同一脚本中操作的、原本可能不属于同一槽位的键,可以使用 哈希标签 (Hash Tags) 。即通过确保键名中 {}
括号内的部分相同,让 Redis 将它们分配至同一个槽。例如:
EVAL "..." 2 {user:1001}:profile {user:1001}:session token 3600
- 这里
{user:1001}
就是哈希标签,它能保证这两个键被分配到相同的哈希槽。
🔧 3. 参数处理技巧
-
类型转换 :通过
ARGV
传递的参数在 Lua 中默认是字符串类型 。如果你需要进行数值计算或比较,务必使用tonumber()
函数进行转换。inilocal increment = tonumber(ARGV[1]) -- 将字符串参数转换为数字 local newValue = redis.call('INCRBY', KEYS[1], increment)
-
避免硬编码 :绝对不要 在脚本中硬编码键名或参数值。所有动态内容都应通过
KEYS
和ARGV
传递,这能提高脚本的通用性和可复用性。
⚡ 4. 性能与可维护性建议
-
保持简洁 :尽量保持
KEYS
和ARGV
的数组大小紧凑,只传递必要的信息,这有助于减少网络开销和内存占用。 -
使用 EVALSHA :为了提高性能并减少网络传输,应先将脚本加载 (
SCRIPT LOAD
) 到 Redis,获取其 SHA1 摘要,然后使用EVALSHA
命令通过该摘要来执行脚本。ini# 先加载脚本,获取SHA1 local sha1 = redis.pcall('SCRIPT', 'LOAD', 'your_lua_script_content') # 之后使用EVALSHA执行 EVALSHA sha1 numkeys key1 key2 arg1 arg2
-
清晰注释 :在复杂的脚本中,清晰注释每个
KEYS
和ARGV
参数的用途,这将大大提升代码的可维护性。
🛡️ 5. 错误处理
-
使用
redis.pcall()
而不是redis.call()
可以在某些场景下更好地处理错误,因为pcall
会在发生错误时捕获异常并以 Lua 表的形式返回错误信息,而不是直接抛出错误导致脚本终止。这允许你在脚本内进行更灵活的错误处理。sqllocal ok, result = pcall(redis.call, 'GET', KEYS[1]) if not ok then -- 处理错误 return {err = result} end
遵循这些最佳实践,你将能编写出更健壮、高效且易于维护的 Redis Lua 脚本。