lua脚本的原子性

在Redis等嵌入Lua的环境中,​Lua脚本的执行具有原子性,即一个Lua脚本在执行期间不会被其他命令或脚本中断。这是通过以下机制实现的:


1. ​单线程模型保证原子性

Redis采用单线程处理命令(6.0后多线程仅用于IO,命令执行仍是单线程)。当执行Lua脚本时:

  • 独占执行​:脚本作为一个整体任务被Redis主线程执行,期间不会处理其他客户端请求。
  • 无并发冲突​:所有脚本内的操作(如多次GET/SET)如同一个事务,中间状态对外不可见。
-- 示例:原子性递增计数器
local current = redis.call('GET', 'counter')
current = tonumber(current) or 0
redis.call('SET', 'counter', current + 1)
-- 其他客户端在此脚本执行期间无法看到中间的counter值

2. ​与事务(MULTI/EXEC)的区别
特性Lua脚本Redis事务(MULTI/EXEC)
原子性绝对原子(整体执行或失败)非原子(仅保证命令顺序执行)
中间结果可见性不可见可见(但会被DISCARD取消)
错误处理脚本报错则全部回滚某条命令失败后继续执行后续命令
-- Lua脚本错误会回滚
redis.call('SET', 'key1', 'value1')
redis.call('INCR', 'key1') -- 如果key1不是数字,整个脚本回滚

3. ​原子性的边界条件
  • 脚本执行超时​:默认5秒(可通过lua-time-limit配置),超时后Redis会记录警告但不会自动终止脚本​(需用SCRIPT KILLSHUTDOWN NOSAVE干预)。
  • 网络中断​:客户端断开连接不会停止脚本执行,服务器会继续执行直至完成。
  • 资源限制​:脚本中若包含慢操作(如KEYS *),会阻塞整个Redis实例。

4. ​实际应用场景
  • 计数器竞赛条件​:
    -- 原子性实现计数器递增
    local current = redis.call('GET', 'counter')
    current = tonumber(current) or 0
    redis.call('SET', 'counter', current + 1)
  • 分布式锁​:
    -- 原子性获取锁
    if redis.call('SETNX', 'mylock', '1') == 1 then
        redis.call('EXPIRE', 'mylock', 10)
        return true
    else
        return false
    end

5. ​注意事项
  1. 避免长脚本​:长时间运行的脚本会导致Redis无法响应其他请求。
  2. 禁用危险操作​:如KEYSFLUSHDB等,应在脚本中避免。
  3. 复用脚本​:使用SCRIPT LOADEVALSHA减少网络传输。

总结

Lua脚本在Redis中的原子性由单线程模型脚本整体执行机制保证,适用于需要强一致性的场景,但需注意性能影响和错误处理。