问题描述
Python Redis 执行 Lua 脚本可实现原子性
官方文档表示,Redis 使用相同的 Lua 解释器来运行所有命令,Redis 保证脚本以原子方式执行:
- 在执行脚本时不会执行其他脚本或 Redis 命令。
- 这种语义类似于
MULTI/EXEC的语义,从所有其他客户的角度来看,脚本的效果要么仍然不可见,要么已经完成。
解决方案
安装
pip install redis
代码
from redis import Redis
redis = Redis()
script = """
redis.call('SETEX', 'key', 100, 'value')
"""
redis.register_script(script)() # 执行脚本
效果

传递参数
Script 类调用函数原型:
def __call__(self, keys=[], args=[], client=None)
from redis import Redis
redis = Redis()
script = """
local key = KEYS[1]
local seconds = ARGV[1]
local value = ARGV[2]
redis.call('SETEX', key, seconds, value)
"""
cmd = redis.register_script(script)
cmd(keys=['key'], args=[100, 'value']) # 执行脚本
效果同上
分布式锁
1.加锁并设过期时间 SET lock_resource_name $uuid NX PX $expire_time,同时启动守护线程为快要过期单还没执行完毕的客户端的锁续命;
2.客户端执行业务逻辑操作共享资源;
3.通过 Lua 脚本释放锁,先 get 判断锁是否是自己加的,再执行 DEL。
判断+删除
import time
from uuid import uuid4
from redis import Redis
redis = Redis()
# 判断是否自己创建的锁,是的话则释放
script = """
if redis.call('GET', KEYS[1]) == ARGV[1] then
return redis.call('DEL', KEYS[1])
else
return 0
end
"""
def do_something():
name = 'lock' # 锁名,此处故意写死让别的进程进来占用
uuid = str(uuid4())
# 上锁
if redis.set(name=name, value=uuid, nx=True, ex=25): # 锁不存在才能上锁成功,过期时间应为业务时间的五倍,此处故意写小,模拟过期释放
print('上锁')
try:
print('处理业务', uuid)
time.sleep(10)
except Exception as e:
print(e)
finally:
# 释放锁
cmd = redis.register_script(script)
res = cmd(keys=[name], args=[uuid]) # 执行脚本
if res:
print('锁已释放')
else:
print('不是自己的锁')
else:
print('锁已存在')
if __name__ == '__main__':
do_something()
模拟过程:
- 运行程序1
- 手动迅速删掉这个锁
- 运行程序2
效果:

总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持IT俱乐部。
