《Redis Lua 脚本实现原子操作》

《Redis Lua 脚本实现原子操作》

Redis 2.6+ 支持在服务端执行 Lua 脚本。一段脚本内的所有命令在 Redis 中串行执行,天然保证原子性,是解决「读-改-写」竞态问题的常用手段。

1 EVAL 基本语法

EVAL script numkeys key [key ...] arg [arg ...]

key 通过 KEYS[1]、KEYS[2] 访问,其他参数通过 ARGV[1]、ARGV[2] 访问。

示例:先读再更新,保证不被并发打断。

local v = tonumber(redis.call('GET', KEYS[1]) or 0) v = v + tonumber(ARGV[1]) redis.call('SET', KEYS[1], v) return v

执行:

redis-cli eval "local v = tonumber(redis.call('GET', KEYS[1]) or 0); v = v + tonumber(ARGV[1]); redis.call('SET', KEYS[1], v); return v" 1 counter 5

2 redis.call 与 redis.pcall

  • redis.call(...):调用 Redis 命令,出错时直接抛出异常,整个 EVAL 会中断。
  • redis.pcall(...):出错时返回 lua table(含 err 字段)而不是抛错,适合需要容错的脚本。

3 redis.call 的返回值

redis.call 的返回值就是脚本内部执行的最后一个命令的返回值,而不是脚本里 return 的内容。两者独立:

  • 想让调用方拿到结果,脚本末尾必须显式 return,脚本的返回值作为 EVAL 的执行结果返回给客户端。
  • 例如 return redis.call('GET', KEYS[1]) 返回 GET 的值。
redis-cli eval "return redis.call('SET', KEYS[1], ARGV[1])" 1 foo bar # OK

4 常用场景

  • 限流(令牌桶 / 滑动窗口计数器)。
  • 分布式锁加锁与释放(SET NX + 校验 value 后 DEL)。
  • 秒杀库存的「检查-扣减」原子操作。
  • 用 SCRIPT LOAD 预加载脚本,再用 EVALSHA 按 SHA 执行,节省带宽。

5 注意事项

  • 脚本中访问的 key 应一次性通过 KEYS 传入,不要在脚本里拼接生成 key,否则集群模式下无法路由。
  • 脚本默认超时时间 lua-time-limit 5000ms,超时仅阻塞脚本执行,其他命令可继续。
  • 参数必须是字符串,数值需用 tonumber() 显式转换。
  • 官方中文文档:http://redisdoc.com/script/eval.html。
阅读 — · 全站 —
🎸 我的歌单 0 首