Redis Lua脚本中全局变量无效且危险,唯一安全读写方式是redis.call();内存管理核心在于键的生命周期控制、聚合存储优化及及时清理脏key。

Redis Lua脚本里不能用全局变量,redis.call() 是唯一安全读写方式
Redis 的 Lua 环境是沙箱化的,所有用户定义的全局变量(比如 my_var = 1)在脚本执行结束后就被销毁,且**同一脚本内多次赋值会污染后续调用**。更关键的是:这些全局变量不共享、不持久、不跨命令存在,根本不是“Redis 全局变量”,只是 Lua 解释器临时栈上的垃圾。
真正能跨调用、带语义、受 Redis 管理的,只有键(key)本身。所以所谓“管理内存”,其实是控制键的生命周期和访问模式。
-
redis.call("SET", "counter", 1)—— 写入键,数据落盘/内存,受 LRU/LFU 或过期策略约束 -
redis.call("GET", "counter")—— 安全读取,自动触发 key miss、过期检查、类型校验 - 别写
counter = counter + 1(这是 Lua 局部变量),它不操作 Redis 数据,也不影响内存占用 - 如果真需要临时中间值,用
local tmp = redis.call("GET", "a") + redis.call("GET", "b"),避免tmp泄露成全局
用 redis.pcall() 替代 redis.call() 时,错误处理不等于绕过内存约束
redis.pcall() 只是让错误不中断脚本执行,但它调用的命令该占内存还占内存,该触发淘汰还是触发淘汰。常见误判是:“我用 pcall 捕获了 ERR value is not an integer,就不用管类型了”——错。类型错误说明你正在操作一个非预期结构的 key,而这个 key 依然活着,持续消耗内存。
- 每次
redis.pcall("INCR", "bad_key")失败,bad_key还在内存里,只是值不是数字 - 长期积累这种“脏 key”,会拖慢
KEYS、SCAN、RDB 快照、AOF 重写 - 正确做法:失败后主动清理,比如
redis.pcall("DEL", "bad_key")(注意也得用pcall防 DEL 报错) -
pcall不改变命令本身的内存行为,它只改异常传播方式
大量小 key 导致内存碎片?用 HASH 或 JSON(RedisJSON)聚合存储
比如记录 10 万个用户的在线状态:user:123:online、user:456:online……这种模式下,每个 key 都有独立的 dictEntry + redisObject 开销(约 96 字节以上),远超实际数据(如一个 "1" 字符)。内存利用率可能低于 10%。
- 改用
HSET user:status 123 "1" 456 "1",一个 key 存全部,底层用 ziplist 或 hashtable,节省 70%+ 内存 - Redis 7.0+ 启用
JSON.SET user:status $ '{"123":1,"456":1}',支持路径更新,但注意 JSON 类型解析开销略高 - 别依赖
CONFIG SET maxmemory-policy allkeys-lru来“兜底”——碎片和元数据膨胀不会被 LRU 清掉 - 用
MEMORY USAGE user:status对比单 key 和多 key 模式的真实开销,别猜
脚本里用 redis.replicate_commands() 会影响 AOF 和从库,但不解决内存问题
默认情况下,Redis Lua 脚本以 EVALSHA 原子形式记录到 AOF 和同步到从库,不展开。启用 redis.replicate_commands() 后,脚本内每个 redis.call() 都变成独立命令追加,看起来“更透明”,但代价明显:
- AOF 文件体积暴涨(尤其循环调用
SET时),重写压力大,加载慢 - 从库回放延迟升高,因为命令变多、串行化开销增
- 它完全不减少内存占用——该存的 key 还在,该占的结构还在
- 仅在调试或需要从库精确看到中间状态时启用,生产环境慎用
local,而是你往数据库里塞了多少 key、用了什么编码、有没有及时清理无效数据。

















