《Redis应用实例》书摘(6):带密码保护功能的锁

一般情况下,除非程序出现bug或者操作错误,没有持有锁的客户端是不应该释放锁的,但为了彻底杜绝这一可能,我们可以给锁加上密码保护功能,使得锁只有在给定正确密码的情况下才会被释放。

代码清单 CODE_IDENTITY_LOCK 展示了根据带有密码保护功能的锁实现。


代码清单 CODE_IDENTITY_LOCK 带有密码保护功能的锁实现 identity_lock.py

from redis import WatchError

class IdentityLock:

    def __init__(self, client, key):
        self.client = client
        self.key = key

    def acquire(self, password):
        """
        尝试获取一个带有密码保护功能的锁,
        成功时返回True,失败时则返回False。
        password参数用于设置上锁/解锁密码。
        """
        return self.client.set(self.key, password, nx=True) is True

    def release(self, password):
        """
        根据给定的密码,尝试释放锁。
        锁存在并且密码正确时返回True,
        返回False则表示密码不正确或者锁已不存在。
        """
        tx = self.client.pipeline()
        try:
            # 监视锁键以防它发生变化
            tx.watch(self.key)
            # 获取锁键存储的密码
            lock_password = tx.get(self.key)
            # 比对密码
            if lock_password == password:
                # 情况1:密码正确,尝试解锁
                tx.multi()
                tx.delete(self.key)
                return tx.execute()[0]==1  # 返回删除结果
            else:
                # 情况2:密码不正确
                tx.unwatch()
        except WatchError:
            # 尝试解锁时发现键已变化
            pass
        finally:
            # 确保连接正确回归连接池,redis-py的要求
            tx.reset()
        # 密码不正确或者尝试解锁时失败
        return False

正如前面所说,程序的acquire()方法会接受一个密码并在之后将其设置为键的值。 与此对应,release()方法在尝试解锁的时候,会先使用WATCH命令监视锁键,接着获取锁键的当前值并与给定密码进行对比:如果对比结果一致,那么程序就会以事务方式尝试删除锁键以释放锁。

以下是这个锁程序的使用方法:

>>> from redis import Redis
>>> from identity_lock import IdentityLock
>>> client = Redis(decode_responses=True)
>>> lock = IdentityLock(client, "Lock:10086")
>>> lock.acquire("top_secret")  # 获取加密锁
True
>>> lock.release("wrong_password")  # 密码错误,解锁失败
False
>>> lock.release("top_secret")  # 密码正确,解锁成功
True

Tip

本文摘录自《Redis应用实例》一书。
欢迎访问书本主页以了解更多Redis使用案例:huangz.works/rediscookbook/
../_images/rediscookbook-banner.png