处理锁持有者崩溃而不释放锁的情况,或者当锁持有者失败并永久持有锁时。我们使用 expire
为锁添加超时,如下所示:
# code snippet 1
def acquire_lock_with_timeout(
conn, lockname, acquire_timeout=10, lock_timeout=10):
identifier = str(uuid.uuid4())
lock_timeout = int(math.ceil(lock_timeout))
end = time.time() + acquire_timeout
while time.time() < end:
# if client happens to crash (between SETNX and EXPIRE)
if conn.setnx(lockname, identifier):
conn.expire(lockname, lock_timeout)
return identifier
# other client will check the expiration on the lock, and if it’s not set, set it.
elif not conn.ttl(lockname):
conn.expire(lockname, lock_timeout)
time.sleep(.001)
return False
<块引用>
为了给我们的锁一个超时时间,我们将使用 EXPIRE 让 Redis 自动超时。放置 EXPIRE 的自然位置是紧接在锁之后 收购,我们会这样做。但是如果我们的客户端碰巧崩溃了(这是最糟糕的地方) 它对我们来说崩溃是在 SETNX 和 EXPIRE 之间),我们仍然希望锁最终 超时。为了处理这种情况,每当客户端无法获得锁时,客户端将 检查锁的到期时间,如果未设置,则设置它。因为客户要 检查和设置超时如果他们无法获得锁定,锁定将始终具有 超时,最终会过期,让其他客户端获得超时锁定。
所以这里的问题是:
我们是否可以像代码片段 2 一样使用 conn.setnx
将 conn.expire
和 multi/exec
包装在 redis 事务中以确保这两个操作的原子性?它会像代码片段 1 一样工作吗?
# code snippet 2
def acquire_lock_with_timeout(
conn, lockname, acquire_timeout=10, lock_timeout=10):
identifier = str(uuid.uuid4())
lock_timeout = int(math.ceil(lock_timeout))
end = time.time() + acquire_timeout
p = conn.pipeline()
while time.time() < end:
p.multi()
if p.setnx(lockname, identifier):
p.expire(lockname, lock_timeout)
if p.execute()[-1]:
return identifier
time.sleep(.001)
return False