编写 Redis Lua 脚本的注意事项:避开这 8 个坑
1. 使用 KEYS 和 ARGV,不要硬编码 key 名
-- ❌ 错误:硬编码 key 名
redis.call('SET', 'mykey', 'value')
-- ✅ 正确:通过 KEYS 数组传递 key 名
redis.call('SET', KEYS[1], ARGV[1])
原因:硬编码 key 名会导致:
– 脚本无法在不同环境(开发/测试/生产)间复用
– Redis Cluster 模式下无法正确路由到对应节点
– 运维时无法判断脚本操作的 key
2. 控制脚本执行时间
-- ❌ 危险:无限循环
while true do
-- 死循环!
end
-- ❌ 危险:大量数据遍历
local keys = redis.call('KEYS', '*') -- 可能包含百万级 key
for i, key in ipairs(keys) do
-- 处理每个 key...
end
最佳实践:
– 脚本执行时间控制在 1ms 以内
– 默认 lua-time-limit 为 5 秒,建议不要接近这个值
– 不要在生产环境调用 KEYS 命令
– 大数据量操作使用 SCAN 替代 KEYS
3. 正确处理数据类型
-- ❌ 错误:假设返回值总是字符串
local val = redis.call('GET', KEYS[1])
local num = tonumber(val) + 1 -- 如果 key 不存在,val 为 false,tonumber 返回 nil
-- ✅ 正确:检查 nil
local val = redis.call('GET', KEYS[1])
if val then
return tonumber(val) + 1
else
return 0
end
4. 避免在脚本中生成随机值
-- ❌ 可能的问题:随机值导致脚本在不同节点上执行结果不同
local random = math.random()
redis.call('SET', KEYS[1], random)
-- ✅ 如果确实需要随机值,通过 ARGV 从客户端传入
redis.call('SET', KEYS[1], ARGV[1])
原因:在 Redis Cluster 中,如果主从切换,从节点执行脚本时可能会产生不同的随机值,导致数据不一致。
5. 谨慎使用全局变量
-- ❌ 错误:使用全局变量
count = 0 -- 全局变量
for i, key in ipairs(KEYS) do
count = count + 1
end
-- ✅ 正确:使用 local
local count = 0
for i, key in ipairs(KEYS) do
count = count + 1
end
原因:Lua 脚本在沙箱环境中执行,全局变量可能被其他脚本污染或导致意外行为。
6. 了解可用的 Redis 函数
可以使用:redis.call()、redis.pcall()、redis.log()、redis.setresp()
redis.call():执行命令,出错时抛出异常redis.pcall():执行命令,出错时返回错误对象(不抛异常)redis.log():打印日志(redis.LOG_WARNING、redis.LOG_NOTICE、redis.LOG_VERBOSE、redis.LOG_DEBUG)
local ok, err = redis.pcall('SET', KEYS[1], ARGV[1])
if not ok then
redis.log(redis.LOG_WARNING, 'SET failed: ' .. tostring(err))
return {err = err}
end
不可用:部分 Lua 标准库函数如 dofile、loadfile、require、io、os 被禁止。
7. 注意返回值格式
-- 返回单个值
redis.call('GET', KEYS[1]) -- 返回字符串
-- 返回多个值
return {redis.call('GET', KEYS[1]), redis.call('GET', KEYS[2])}
-- 返回 status reply
return redis.status_reply('OK')
-- 返回 error reply
return redis.error_reply('Something went wrong')
8. 测试和监控
-- 开发调试:使用 redis.log 打印信息
redis.log(redis.LOG_NOTICE, "Starting balance check for " .. KEYS[1])
local balance = redis.call('GET', KEYS[1])
redis.log(redis.LOG_NOTICE, "Current balance: " .. tostring(balance))
性能检查清单
-- 高效脚本模板
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local ttl = tonumber(ARGV[2])
-- ✅ 所有变量都是 local
-- ✅ 没有遍历大量数据
-- ✅ 没有硬编码 key
-- ✅ 执行时间可控
-- ✅ 正确处理 nil 值
local current = redis.call('GET', key)
if current and tonumber(current) >= limit then
return 0
end
redis.call('INCR', key)
redis.call('EXPIRE', key, ttl)
return 1
面试要点
- 一定要用
local声明变量 - 脚本内不可调用
KEYS *等慢命令 - 执行时间控制在毫秒级
- Redis Cluster 下脚本内 key 必须在 slot 相同的节点上
- 用
redis.pcall()代替redis.call()处理可选错误
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END


暂无评论内容