一、Lua脚本在Redis中的基本玩法
Redis支持用Lua脚本执行复杂操作,这相当于把多个命令打包成一个原子操作。比如你想同时更新某个键的值并设置过期时间,用Lua脚本可以避免竞态条件。
-- 技术栈:Redis + Lua
-- 示例:原子性递增并设置过期时间
local key = KEYS[1] -- 从参数获取键名
local expire_time = ARGV[1] -- 获取过期时间(秒)
local new_val = redis.call('INCR', key) -- 执行递增
if new_val == 1 then -- 如果是第一次操作
redis.call('EXPIRE', key, expire_time) -- 设置过期
end
return new_val -- 返回新值
注意点:
KEYS和ARGV是Lua脚本的固定参数,KEYS用于传递键名,ARGV传其他参数。- 脚本里所有Redis操作都是原子性的,不用担心其他客户端插队。
二、性能优化的关键技巧
1. 脚本缓存机制
每次执行EVAL命令时,Redis需要重新编译脚本。用SCRIPT LOAD+EVALSHA可以避免重复编译:
-- 技术栈:Redis + Lua
-- 先加载脚本获取SHA1摘要
local script = [[
return redis.call('GET', KEYS[1])
]]
local sha1 = redis.call('SCRIPT', 'LOAD', script)
-- 后续通过摘要调用
redis.call('EVALSHA', sha1, 1, 'my_key')
2. 避免大Key操作
Lua脚本执行时会阻塞Redis,操作大Key可能导致长时间阻塞:
-- 反例:一次性获取大Hash所有字段
local big_data = redis.call('HGETALL', 'huge_hash') -- 可能阻塞!
-- 正例:分批扫描
local cursor = 0
repeat
local result = redis.call('HSCAN', 'huge_hash', cursor)
cursor = tonumber(result[1])
-- 处理result[2]中的数据
until cursor == 0
三、高级应用场景
1. 分布式锁进阶版
用Lua实现带自动续期的锁:
-- 技术栈:Redis + Lua
-- 参数:锁键、客户端ID、过期时间
local key = KEYS[1]
local client_id = ARGV[1]
local ttl = ARGV[2]
-- 检查是否持有锁
if redis.call('GET', key) == client_id then
redis.call('EXPIRE', key, ttl) -- 续期
return true
else
return false
end
2. 秒杀库存扣减
经典秒杀场景中保证原子性:
-- 技术栈:Redis + Lua
local stock_key = KEYS[1]
local user_id = ARGV[1]
-- 检查库存
local stock = tonumber(redis.call('GET', stock_key))
if stock <= 0 then
return 0
end
-- 扣减库存并记录用户
redis.call('DECR', stock_key)
redis.call('SADD', 'seckill:success', user_id)
return 1
四、避坑指南
不要写死循环:
Redis默认配置了lua-time-limit(通常5秒),超时会导致脚本被强制终止。参数类型转换:
Lua的数字是浮点型,Redis返回的整数需要显式转换:local num = tonumber(redis.call('GET', 'counter')) -- 必须转换!集群环境注意:
所有操作的Key必须在同一个Redis节点,可以用{}强制哈希标签:-- 保证user123相关的key落在同一节点 redis.call('SET', 'user{123}:name', 'Alice') redis.call('SET', 'user{123}:age', 30)
五、总结
Lua脚本在Redis中就像瑞士军刀,能解决原子性、批量操作等难题。但记住:
- 简单操作优先用原生命令
- 复杂逻辑才考虑Lua脚本
- 一定要测试脚本性能影响
合理使用这个特性,你的Redis就能既保持高性能,又能处理复杂业务逻辑!
评论