应避免在循环中用..拼接字符串,因其每次创建新字符串对象且旧对象滞留等待GC,导致jemalloc small-bin碎片堆积、mem_fragmentation_ratio升高;须改用table.insert收集后table.concat一次性拼接,并显式处理redis.call返回nil的情况。

避免在循环里用 .. 拼接字符串
直接写 res = res .. redis.call("GET", k) 看似直观,但每次拼接都会创建新字符串对象,旧字符串滞留等待 GC。Lua 5.1/5.2 的增量 GC 不保证及时回收,大量短生命周期小字符串堆积在 jemalloc 的 small-bin 中,推高 mem_fragmentation_ratio,长期运行后内存使用率异常升高。
- 改用
table.insert收集结果,最后一次性table.concat - 若需分隔符,直接传给
table.concat(parts, "|"),别在循环里反复拼接 - 不要用
string.format("%s:%s", a, b)替代a .. ":" .. b——前者开销更大、临时对象更多
redis.call() 返回值为 nil 时的拼接风险
redis.call("GET", key) 在键不存在时返回 nil,而 nil 无法参与 .. 拼接,会直接报错:attempt to concatenate a nil value。这不是语法错误,是运行时崩溃,且脚本会中断执行。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 必须显式判断:
if not val then return error("key missing") - 或用默认值兜底:
val or "",但要注意语义是否合理(比如空字符串和缺失状态应区分) - 批量读取时,建议统一检查:
for i, k in ipairs(KEYS) do local v = redis.call("GET", k); if not v then return error("missing key: " .. k) end; table.insert(parts, v) end
固定格式输出优先用字面量,而非运行时构造
像 {"code":0,"msg":"ok"} 这类结构简单、内容固定的响应,硬编码字符串比用 string.format 或表拼接更安全、更快。
- 字面量字符串无 GC 压力,不触发 jemalloc 小块分配
- 避免
string.format('{ "code": %d, "msg": "%s" }', code, msg)—— 它会生成多个中间 string 对象,且对msg中特殊字符无转义,有注入风险 - 如果必须动态生成 JSON,应确认 Redis 版本是否支持
cjson库(8.2.3+ 默认禁用,需手动加载并注意性能代价)
表创建要节制,尤其在高频脚本中
每个 {} 或 table.new 都触发一次 jemalloc 分配。高频调用下,哪怕只含 1–2 个字段的临时表(如 {status="ok", ts=redis.call("TIME")[1]})也会加剧碎片积累。
- 能复用就复用:提前声明
local result = {},循环内只赋值,不重复= {} - 避免嵌套表:如
{data={x=1}}比{data_x=1}多一次分配 - Redis 8.2.3 开始,默认禁止
loadstring和自定义 C 模块,table.new的预分配能力受限,老式写法更稳妥
redis.call 返回值隐式转换带来的类型陷阱。写完脚本后,务必用 redis-cli --eval 实测 + 观察 INFO memory 中的 mem_fragmentation_ratio 变化。

















