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


暂无评论内容