dot Redis 8 来了—而且它是开源的

了解更多

6.2.5 带超时时间的锁

返回首页

6.2.5 带超时时间的锁

如前所述,我们的锁不能处理锁持有者在未释放锁的情况下崩溃,或锁持有者失败并永久持有锁的情况。为了处理崩溃/失败的情况,我们为锁添加一个超时时间。

为了给我们的锁添加超时时间,我们将使用 EXPIRE 让 Redis 自动使其超时。放置 EXPIRE 的自然位置是在获取锁之后立即放置,我们将这样做。但是,如果我们的客户端碰巧崩溃了(对我们来说最糟糕的崩溃位置是在 SETNXEXPIRE 之间),我们仍然希望锁最终超时。为了处理这种情况,任何客户端在未能获得锁时,都会检查锁的过期时间,如果未设置,则进行设置。由于客户端在未能获得锁时将检查和设置超时时间,因此锁将始终具有超时时间,并最终过期,从而让其他客户端获得超时的锁。

如果多个客户端同时设置过期时间会怎么样?它们将几乎同时运行,因此过期时间将被设置为相同的时间。

将过期时间添加到我们之前的 acquire_lock() 函数中,就可以得到此处显示的更新后的 acquire_lock_with_timeout() 函数。

列表 6.11 acquire_lock_with_timeout() 函数
def acquire_lock_with_timeout(
   conn, lockname, acquire_timeout=10, lock_timeout=10):
 
   identifier = str(uuid.uuid4())

一个 128 位的随机标识符。

   lock_timeout = int(math.ceil(lock_timeout))

仅将整数传递给我们的 EXPIRE 调用。

   end = time.time() + acquire_timeout
   while time.time() < end:
 
      if conn.setnx(lockname, identifier):
         conn.expire(lockname, lock_timeout)

获取锁并设置过期时间。

         return identifier
 
      elif not conn.ttl(lockname):
         conn.expire(lockname, lock_timeout)

根据需要检查和更新过期时间。

      time.sleep

   return False
 

 

这个新的 acquire_lock_with_timeout() 处理超时。它确保锁在必要时过期,并且它们不会从合法拥有它们的客户端那里被盗用。更好的是,我们之前使用的释放锁函数非常智能,仍然可以正常工作。

注意从 Redis 2.6.12 开始,SET 命令添加了选项以支持 SETNXSETEX 功能的组合,这使我们的锁获取函数变得微不足道。我们仍然需要复杂的释放锁才能正确。

在 6.1.2 节中,当我们使用 ZSET 构建地址簿自动完成功能时,我们遇到了一些麻烦来创建开始和结束条目以添加到 ZSET 中,以便获取一个范围。我们还对数据进行了后处理以删除带有大括号 ({}) 的条目,因为其他自动完成操作可能同时进行。并且由于其他操作可能同时进行,因此我们使用 WATCH 以便我们可以重试。所有这些都增加了我们函数的复杂性,如果使用锁代替,这些复杂性就可以简化。

在其他数据库中,锁定是受支持并自动执行的基本操作。正如我之前提到的,使用 WATCHMULTIEXEC 是一种乐观锁的方式——我们实际上并没有锁定数据,但如果其他人在我们修改之前修改了数据,我们会收到通知并且我们的更改会被取消。通过在客户端上添加显式锁定,我们可以获得一些好处(更好的性能、更熟悉的编程概念、更易于使用的 API 等),但我们需要记住 Redis 本身不尊重我们的锁。除了或代替 WATCHMULTIEXEC 之外,我们还需要始终如一地使用我们的锁,以保持我们数据的一致性和正确性。

现在我们已经构建了一个带超时时间的锁,让我们看看另一种称为计数信号量的锁。它不像常规锁那样在许多地方使用,但是当我们需要允许多个客户端同时访问相同的信息时,它是完成这项工作的完美工具。