问题描述
Python Redis 执行 Lua 脚本可实现原子性
官方文档表示,Redis 使用相同的 Lua 解释器来运行所有命令,Redis 保证脚本以原子方式执行:
- 在执行脚本时不会执行其他脚本或 Redis 命令。
- 这种语义类似于
MULTI
/EXEC
的语义,从所有其他客户的角度来看,脚本的效果要么仍然不可见,要么已经完成。
解决方案
安装
1 | pip install redis |
代码
1 2 3 4 5 6 7 | from redis import Redis redis = Redis() script = """ redis.call('SETEX', 'key', 100, 'value') """ redis.register_script(script)() # 执行脚本 |
效果
传递参数
Script 类调用函数原型:
1 | def __call__( self , keys = [], args = [], client = None ) |
1 2 3 4 5 6 7 8 9 10 11 | 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
。
判断+删除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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俱乐部。