Lua 脚本在 Redis 中的作用与优势:为什么它是 Redis 的”瑞士军刀”

Lua 脚本在 Redis 中的作用与优势:为什么它是 Redis 的”瑞士军刀”

Lua 脚本在 Redis 中的定位

Redis 从 2.6 版本引入 Lua 脚本支持,通过内置的 Lua 5.1 解释器,允许用户执行自定义脚本。Lua 脚本是 Redis 提供复杂业务逻辑能力的核心手段,让你把多条命令打包成一个原子操作。

Lua 脚本的作用

1. 实现复杂业务逻辑

把多条 Redis 命令组合在一个脚本中执行,实现条件判断、循环等逻辑:

-- 限流脚本:窗口内限制访问次数
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local ttl = tonumber(ARGV[2])

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  -- 允许访问

2. 替代事务实现原子操作

Lua 脚本天然原子执行,比 MULTI/EXEC 更灵活,不需要 WATCH 重试。

3. 减少网络往返

将多条语句打包成一个脚本,一次 EVAL 调用即可完成复杂操作。

4. 复用业务逻辑

通过 SCRIPT LOAD + EVALSHA 模式,将脚本常驻服务端,客户端只需传递 SHA 签名即可调用。

Lua 脚本的核心优势

优势一:原子性(最重要的优势)

Lua 脚本执行期间,Redis 不会执行任何其他命令。这意味着脚本中的操作要么全部执行完,要么全部不执行,不存在中间状态。这是 Redis 实现分布式锁、限流、秒杀等操作的基础保障。

优势二:减少网络开销

# 无 Lua 脚本:多次网络往返
r.get('key1')
r.get('key2')
r.set('key3', 'val')

# 有 Lua 脚本:一次网络往返
script = """
local a = redis.call('GET', KEYS[1])
local b = redis.call('GET', KEYS[2])
redis.call('SET', KEYS[3], ARGV[1])
return {a, b}
"""
r.eval(script, 3, 'key1', 'key2', 'key3', 'val')

在高并发场景下,减少网络往返的效果尤为显著。

优势三:逻辑封装与复用

业务逻辑集中在脚本中,客户端代码更简洁。配合 SCRIPT LOAD,脚本编译后常驻 Redis,后续调用只需传递 SHA。

SCRIPT LOAD "return redis.call('SET', KEYS[1], ARGV[1])"
-- 返回: "b8059f...a3"
EVALSHA b8059f...a3 1 mykey myvalue

优势四:避免竞态条件

-- 检查并设置:避免竞态条件
if redis.call('EXISTS', KEYS[1]) == 0 then
    redis.call('SET', KEYS[1], ARGV[1])
    redis.call('EXPIRE', KEYS[1], ARGV[2])
    return 1
else
    return 0
end

这个 SETNX 的增强版(SETNX + EXPIRE 原子化)如果通过客户端分两步执行,会在两步之间产生竞态条件。用 Lua 脚本就完美解决了。

适用场景

场景 为什么用 Lua
分布式锁 需要原子性检查-设置
限流/计数器 需要条件判断+多个操作
秒杀/库存扣减 需要原子性扣减+校验
批量 GET 多个 key 减少网络往返
条件更新 检查条件后再更新

面试要点

  • Lua 脚本最大的优势是原子性
  • 配合 EVALSHA 可以减少带宽开销
  • 适合代替 WATCH+重试的 CAS 模式
  • 脚本内尽量使用 KEYS[i] 和 ARGV[i],避免硬编码 key 名
  • 脚本执行时间不宜过长(默认 5s 限制)
© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容