1 简单分布式锁
1 加锁:SET $lock_key $unique_id EX $expire_time NX
2 操作共享资源
3 释放锁:先 GET 判断锁是否归属自己,再 DEL 释放锁。Lua 脚本(或使用redis事务)保证两步操作的原子性。
使用lua脚本:
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
import redis
def release_lock(redis_conn, lock_key, lock_value):
# 定义释放锁的 Lua 脚本
release_lock_script = """
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
"""
# 执行 Lua 脚本
result = redis_conn.eval(release_lock_script, 1, lock_key, lock_value)
return result == 1
使用redis事务:
import redis
def release_lock(redis_conn, lock_key, lock_value):
# 开启 Redis 事务
with redis_conn.pipeline() as pipe:
while True:
try:
# 监视锁的键,确保在事务执行期间锁没有被修改
pipe.watch(lock_key)
# 获取当前锁的值
current_value = pipe.get(lock_key)
# 检查锁是否存在且与当前客户端持有的锁值相同
if current_value and current_value.decode("utf-8") == lock_value:
# 在事务中删除锁
pipe.multi()
pipe.delete(lock_key)
pipe.execute()
return True
else:
# 锁已经被其他客户端修改,取消事务
pipe.unwatch()
break
except redis.WatchError:
# 如果其他客户端修改了锁,重试
continue
return False
[1]为什么使用lua脚本可以保证释放锁时的原子性?
Lua 脚本在 Redis的执行是原子性的,这主要是由于 Redis 本身的单线程执行模型以及 Lua 脚本的执行机制保证的。在 Redis 中,每个客户端的命令都是由 Redis 服务器单线程依次执行的,这确保了多个命令的原子性执行。
当执行 Lua 脚本时,Redis 会将 Lua 脚本作为整体进行执行,不允许其被其他客户端的操作打断。这样可以保证 Lua 脚本在执行时不会被其他操作打断,从而保证了 Lua 脚本的原子性。
此外,Redis 在执行 Lua 脚本时,使用了 EVAL 命令将 Lua 脚本传递给 Redis 服务器,然后 Redis 服务器会将 Lua 脚本编译为字节码并缓存起来,这样可以提高 Lua 脚本的执行效率,同时也可以保证 Lua 脚本执行的原子性。
[2]为什么可以使用lua脚本,不能使用python脚本?
Python 是一种多线程语言,而 Lua 是单线程语言。在 Python 中,虽然有多线程支持,但由于全局解释器锁(GIL)的存在,多个线程并不能真正并行执行 Python 代码,因此在多线程环境下,Python 会存在线程切换的情况。
而 Lua 是单线程语言,不具备多线程支持。在执行 Lua 脚本时,只有一个 Lua 解释器负责解析和执行脚本,因此不会存在多个线程之间的切换。这意味着在执行 Lua 脚本时,整个过程都是顺序执行的,不会被中断或切换到其他线程执行。
所以,你可以理解为 Python 在多线程环境下可能存在线程切换,而 Lua 在执行脚本时是单线程的,不存在线程切换。这也是 Lua 脚本执行原子性的一个重要原因。
4 锁过期时间不好评估怎么办?
锁的过期时间如果评估不好,这个锁就会有「提前」过期的风险。
妥协方案是,尽量「冗余」过期时间,降低锁提前过期的概率。这个方案其实也不能完美解决问题,那怎么办呢?
可以设计这样的方案:加锁时,先设置一个过期时间,然后我们开启一个「守护
线程」,定时去检测这个锁的失效时间,如果锁快要过期了,操作共享资源还未完成,那么就自动对锁进行「续期」,重新设置过期时间。
在python中有封装好的redis-lock库
python
import time
from redis import Redis
from redis_lock import Lock
# 连接 Redis
redis = Redis(host='localhost', port=6379, db=0)
# 创建一个锁
lock = Lock(redis, 'my_lock')
# 获取锁
with lock:
print("获得锁,执行任务...")
time.sleep(5) # 模拟执行任务的过程
print("任务执行完成")
# 锁自动释放后,执行其他操作
print("锁已释放,执行其他操作...")
在上面的示例中,我们首先创建了一个 Redis 连接对象 redis,然后创建了一个名为 my_lock 的锁对象 lock。接着,我们使用 with 语句来获取锁,并在获取锁成功后执行任务。在任务执行完成后,锁会自动释放,然后执行其他操作。
使用 redis-lock 可以很方便地实现分布式系统中的锁机制,确保在多个客户端之间对共享资源的安全访问。