redis lua脚本中写counter = 1不会报错但极不安全:变量不持久、不共享、不参与内存管理,且可能因lua解释器复用导致栈残留污染后续调用,脱离lru/过期机制,造成内存异常增长却无法追踪。

Redis Lua脚本里写 counter = 1 会出什么问题
它不会报错,但也不会持久、不共享、不安全。Redis 的 Lua 解释器是严格沙箱化的:每次 EVAL 执行完,所有用户定义的全局变量(比如 counter = 1、data = {})立刻被销毁,且**同一连接内多次执行该脚本时,前一次的全局变量可能污染后一次的逻辑**。
更隐蔽的风险是:这些变量完全脱离 Redis 的内存管理机制——它们不参与 LRU/LFU 淘汰,不触发过期检查,也不计入 INFO memory 统计。你看到内存涨了,却查不到对应 key。
- 全局赋值
my_flag = true后,下一次调用脚本时my_flag可能仍是true(Lua 解释器复用导致栈残留) - 用
table.insert(my_list, val)累积数据,my_list实际是未声明的全局 table,极易在并发调用中互相覆盖 - 调试时加的
print(debug.traceback())如果依赖全局 logger,可能在高并发下输出错乱或崩溃
KEYS 和 ARGV 是唯一受控的“外部输入通道”
Redis 显式暴露 KEYS(数组,下标从 1 开始)和 ARGV(数组,下标从 1 开始)两个只读全局变量,这是脚本与外界通信的**合法且受校验的接口**。所有业务参数必须走这里,而不是拼接字符串或硬编码。
硬编码 key 名(如 redis.call("GET", "user:123"))会导致脚本无法复用、无法被 Redis 缓存 SHA1(影响性能),还可能因 key 名冲突引发误操作。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 正确写法:
redis.call("GET", KEYS[1]),调用时传EVAL "...” 1 user:123 - 错误写法:
redis.call("GET", "user:" .. ARGV[1])—— 字符串拼接绕过 key 预检,集群模式下可能路由失败 -
KEYS中的 key 必须全部参与命令执行,否则EVAL在 Redis Cluster 中会报CROSSSLOT错误
redis.call() 和 redis.pcall() 不是“兜底”,而是内存操作的边界
很多人以为用 redis.pcall() 就能随便试错,其实它只是把错误转成 Lua table 返回,**底层命令该申请内存、该写 AOF、该触发淘汰,一步都不会少**。一个失败的 INCR 仍会让目标 key 占着内存,尤其当 value 是字符串而非数字时,这个 key 就成了“脏 key”。
- 执行
redis.pcall("INCR", "counter")失败后,应立刻补一句redis.pcall("DEL", "counter")清理(注意也得用pcall,避免 DEL 报错中断) -
redis.call("HMGET", KEYS[1], unpack(ARGV))中若ARGV是空表,unpack 会返回 nil,导致命令变成HMGET key—— 这是语法错误,call直接崩,pcall返回错误但 key 依旧存在 - 批量操作慎用
redis.call("MSET", ...),大体积ARGV会显著增加脚本解析开销,建议拆成管道或分片
局部变量不是万能的,类型误判比变量作用域更危险
用 local tmp = redis.call("GET", KEYS[1]) 是对的,但接下来如果直接 tmp + 1,就默认 tmp 是 number——而 GET 返回可能是 nil(key 不存在)或 string(比如 “5”)。Lua 不自动类型转换,nil + 1 报错,"5" + 1 却能算出 6,但你根本没意识到 value 被当字符串存进去了。
- 始终显式校验:
if type(tmp) ~= "number" then error("expected number, got " .. type(tmp)) end - 用
tonumber()而非强制相加:local n = tonumber(tmp); if not n then error("invalid number") end - 对 table 类型结果(如
LRANGE返回数组),别直接#res取长度——Lua table 的长度操作在稀疏数组上不可靠,改用table.getn()或遍历计数
真正难防的不是变量声明方式,而是你以为自己在操作 Redis 数据,实际上只在 Lua 栈上玩字符串和数字;等发现内存异常增长、SCAN 慢、RDB 变大时,往往已经积累了几千个没人认领的脏 key。










