一、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             -- 返回新值

注意点

  1. KEYSARGV是Lua脚本的固定参数,KEYS用于传递键名,ARGV传其他参数。
  2. 脚本里所有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

四、避坑指南

  1. 不要写死循环
    Redis默认配置了lua-time-limit(通常5秒),超时会导致脚本被强制终止。

  2. 参数类型转换
    Lua的数字是浮点型,Redis返回的整数需要显式转换:

    local num = tonumber(redis.call('GET', 'counter')) -- 必须转换!
    
  3. 集群环境注意
    所有操作的Key必须在同一个Redis节点,可以用{}强制哈希标签:

    -- 保证user123相关的key落在同一节点
    redis.call('SET', 'user{123}:name', 'Alice')
    redis.call('SET', 'user{123}:age', 30)
    

五、总结

Lua脚本在Redis中就像瑞士军刀,能解决原子性、批量操作等难题。但记住:

  • 简单操作优先用原生命令
  • 复杂逻辑才考虑Lua脚本
  • 一定要测试脚本性能影响

合理使用这个特性,你的Redis就能既保持高性能,又能处理复杂业务逻辑!