《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。
阅读 —
·
全站 —