温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Redis的Lua脚本编程指南

发布时间:2025-11-16 00:54:25 来源:亿速云 阅读:159 作者:小樊 栏目:关系型数据库

Redis Lua脚本编程指南

一 核心概念与优势

  • 原子性:脚本在服务端以单线程方式执行,期间不会插入其他命令,适合把多步操作打包成“一个不可被打断”的整体。
  • 减少网络往返:将多次命令合并为一次脚本调用,显著降低RTT与连接开销。
  • 灵活表达复杂逻辑:支持条件、循环、函数等,弥补单条命令的表达力不足。
  • 版本与沙箱:Redis内建Lua 5.1解释器,提供受限的标准库;出于安全与可移植性,脚本应避免使用全局变量,且不要依赖外部网络/文件IO。
  • 集群路由:在Redis Cluster中,务必通过KEYS显式声明所有键,确保脚本只访问同一哈希槽,避免跨槽错误。

二 执行与管理脚本

  • 常用命令一览
命令 作用 示例
EVAL 直接执行脚本 EVAL “return redis.call(‘GET’,KEYS[1])” 1 mykey
SCRIPT LOAD 将脚本加载进缓存并返回SHA1 SCRIPT LOAD “return ‘Hello’”
EVALSHA 通过SHA1执行已缓存脚本 EVALSHA 1 mykey
SCRIPT EXISTS 检查脚本是否已缓存 SCRIPT EXISTS
SCRIPT FLUSH 清空脚本缓存(Redis 4.0+ 支持 ASYNC) SCRIPT FLUSH [ASYNC]
SCRIPT KILL 终止正在执行的脚本(仅当脚本未修改数据时可用) SCRIPT KILL
SCRIPT DEBUG 开启/关闭脚本调试模式(便于排查) SCRIPT DEBUG on/off
  • 基本用法示例
    • 直接执行:EVAL “return redis.call(‘GET’,KEYS[1])” 1 mykey
    • 缓存执行:
      • SCRIPT LOAD “return ‘Hello’” → 返回 SHA1
      • EVALSHA 0
  • 最佳实践:生产环境优先使用EVALSHA配合SCRIPT LOAD,减少脚本内容在网络中的传输;上线前用SCRIPT EXISTS探测是否已缓存。

三 脚本编写规范与数据类型

  • 参数规范
    • 所有键名必须放入KEYS数组(如:KEYS[1]),其它参数放入ARGV(如:ARGV[1]);严禁在脚本中硬编码键名。
    • 变量一律使用local声明,避免污染全局环境。
  • 调用Redis命令
    • 使用redis.call()执行命令;若需捕获错误而不抛异常,使用redis.pcall()。
  • 返回值与类型映射
Redis 回复类型 Lua 类型
integer reply number
bulk reply string
multi bulk table
status reply table 且含字段 ok
error reply table 且含字段 err
nil reply false
  • 常见控制结构
    • 条件:if/elseif/else;循环:for/while;函数:local function。
  • 性能与阻塞
    • 脚本在单线程中运行,避免长耗时计算或大循环,以免阻塞其它请求。
  • 调试与错误处理
    • 使用SCRIPT DEBUG on获取更详细的执行错误信息;在脚本中区分redis.call(出错抛异常)与redis.pcall(返回错误表)以便容错。

四 实战示例

  • 示例1 原子计数器(越界重置)
-- KEYS[1]: 计数器键;ARGV[1]: 最大值
local cur = tonumber(redis.call('GET', KEYS[1]) or 0)
local max = tonumber(ARGV[1])
local nxt = cur + 1
if nxt > max then nxt = 0 end
redis.call('SET', KEYS[1], nxt)
return nxt
  • 示例2 分布式锁(SET + PX 原子加锁,带值校验与续期)
-- KEYS[1]: 锁键;ARGV[1]: 唯一标识(如UUID);ARGV[2]: 过期毫秒
local key, token, ttl = KEYS[1], ARGV[1], tonumber(ARGV[2])
local ok = redis.call('SET', key, token, 'NX', 'PX', ttl)
if ok then
  return 1  -- 加锁成功
else
  -- 仅当值匹配时续期,避免误续他人锁
  local cur = redis.call('GET', key)
  if cur == token then
    redis.call('PEXPIRE', key, ttl)
    return 1  -- 续期成功
  end
  return 0  -- 加锁失败
end
  • 示例3 固定窗口限流(每分钟最多 N 次)
-- KEYS[1]: 计数器键;ARGV[1]: 阈值;ARGV[2]: 窗口秒数
local key, limit, window = KEYS[1], tonumber(ARGV[1]), tonumber(ARGV[2])
local cur = redis.call('INCR', key)
if cur == 1 then
  redis.call('EXPIRE', key, window)  -- 首次访问设置过期
end
if cur > limit then
  return 0  -- 拒绝
else
  return 1  -- 放行
end
  • 示例4 库存扣减(原子判断与扣减)
-- KEYS[1]: 库存键;KEYS[2]: 已购集合(去重);ARGV[1]: 扣减数量;ARGV[2]: 用户ID
local stock = tonumber(redis.call('GET', KEYS[1]))
local deduct = tonumber(ARGV[1])
if not stock or stock < deduct then
  return 0  -- 库存不足
end
redis.call('DECRBY', KEYS[1], deduct)
redis.call('SADD', KEYS[2], ARGV[2])
return 1  -- 扣减成功
  • 调用要点
    • 锁/限流/库存等场景优先使用EVALSHA;键名全部通过KEYS传入,保证集群正确路由与原子性。

五 最佳实践与常见坑

  • 只通过KEYS/ARGV传参,严禁在脚本中拼接或硬编码键名;在Redis Cluster下,确保脚本只访问同一哈希槽。
  • 变量一律local,避免副作用;脚本保持短小高效,避免阻塞事件循环。
  • 优先使用redis.pcall()捕获命令错误并做降级处理;需要中断脚本时,确保不会破坏数据一致性(否则SCRIPT KILL不可用)。
  • 上线前用SCRIPT LOAD + SCRIPT EXISTS管理脚本缓存;版本发布或热更新时配合**SCRIPT FLUSH [ASYNC]**清理旧脚本。
  • 锁实现务必带唯一标识与过期时间,释放时校验值匹配;仅续期自己的锁,避免误删。
  • 限流尽量使用固定窗口或滑动窗口的成熟实现,注意过期时间设置与冷启动问题。
  • 调试阶段开启SCRIPT DEBUG,生产关闭;对关键路径增加监控与超时保护。
向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI
助
手