示例:带有身份验证功能的锁
在了解了乐观锁机制的使用方法之后,现在是时候使用它来实现一个正确的带身份验证功能的锁了。
之前展示的锁实现的问题在于,在 GET
命令执行之后直到 DEL
命令执行之前的这段时间里,锁键的值有可能会发生变化,并出现误删锁键的情况。为了解决这个问题,我们需要使用乐观锁去保证 DEL
命令只会在锁键的值没有发生任何变化的情况下执行,代码清单 13-6 展示了修改之后的锁实现。
代码清单 13-6 带有身份验证功能的锁实现:/pipeline-and-transaction/identity_lock.py
- from redis import WatchError
- class IdentityLock:
- def __init__(self, client, key):
- self.client = client
- self.key = key
- def acquire(self, identity, timeout):
- """
- 尝试获取一个带有身份标识符和最大使用时限的锁,
- 成功时返回 True ,失败时返回 False 。
- """
- result = self.client.set(self.key, identity, ex=timeout, nx=True)
- return result is not None
- def release(self, input_identity):
- """
- 根据给定的标识符,尝试释放锁。
- 返回 True 表示释放成功;
- 返回 False 则表示给定的标识符与锁持有者的标识符并不相同,释放请求被拒绝。
- """
- # 开启流水线
- pipe = self.client.pipeline()
- try:
- # 监视锁键
- pipe.watch(self.key)
- # 获取锁键储存的标识符
- lock_identity = pipe.get(self.key)
- if lock_identity is None:
- # 如果锁键的标识符为空,那么说明锁已经被释放
- return True
- elif input_identity == lock_identity:
- # 如果给定的标识符与锁键储存的标识符相同,那么释放这个锁
- # 为了确保 DEL 命令在执行时的安全性,我们需要使用事务去包裹它
- pipe.multi()
- pipe.delete(self.key)
- pipe.execute()
- return True
- else:
- # 如果给定的标识符与锁键储存的标识符并不相同
- # 那么说明当前客户端不是锁的持有者
- # 拒绝本次释放请求
- return False
- except WatchError:
- # 抛出异常说明在 DEL 命令执行之前,已经有其他客户端修改了锁键
- return False
- finally:
- # 取消对键的监视
- pipe.unwatch()
- # 因为 redis-py 在执行 WATCH 命令期间,会将流水线与单个连接进行绑定
- # 所以在执行完 WATCH 命令之后,必须调用 reset() 方法将连接归还给连接池
- pipe.reset()
注意,因为乐观锁的效果只会在同时使用 WATCH
命令以及事务的情况下产生,所以程序除了需要使用 WATCH
命令对锁键实施监视之外,还需要将 DEL
命令包裹在事务里面,这样才能确保 DEL
命令只会在锁键的值没有发生任何变化的情况下执行。
以下代码展示了这个锁实现的使用方法:
- >>> from redis import Redis
- >>> from identity_lock import IdentityLock
- >>> client = Redis(decode_responses=True)
- >>> lock = IdentityLock(client, "test-lock")
- >>> lock.acquire("peter", 3600) # 使用 "peter" 作为标识符,获取一个使用时限为 3600 秒的锁
- True
- >>> lock.release("tom") # 尝试使用错误的标识符去释放锁,失败
- False
- >>> lock.release("peter") # 使用正确的标识符去释放锁,成功
- True