通过redis的SETNX/BLPOP实现锁的上下文管理器。
项目描述
通过redis的SETNX/BLPOP实现锁的上下文管理器。
免费软件:BSD 2-Clause许可证
接口目标是完全类似于threading.Lock。
用法
因为我们不想要求用户在进程间共享锁实例,所以您必须给他们命名。
from redis import Redis
conn = Redis()
import redis_lock
lock = redis_lock.Lock(conn, "name-of-the-lock")
if lock.acquire(blocking=False):
print("Got the lock.")
lock.release()
else:
print("Someone else has the lock.")
作为上下文管理器的锁
conn = StrictRedis()
with redis_lock.Lock(conn, "name-of-the-lock"):
print("Got the lock. Doing some work ...")
time.sleep(5)
您还可以将一个标识符与锁相关联,以便稍后由相同的进程或不同的进程检索。这在应用程序需要识别锁所有者(找出谁目前拥有锁)的情况下很有用。
import socket
host_id = "owned-by-%s" % socket.gethostname()
lock = redis_lock.Lock(conn, "name-of-the-lock", id=host_id)
if lock.acquire(blocking=False):
assert lock.locked() is True
print("Got the lock.")
lock.release()
else:
if lock.get_owner_id() == host_id:
print("I already acquired this in another process.")
else:
print("The lock is held on another machine.")
避免在django中发生犬群效应
犬群效应也称为雷鸣群效应或缓存溃败。以下是一种避免问题而不提供陈旧数据的方法。工作将只执行一次,每个客户端都将等待新鲜数据。
要使用此功能,您需要安装django-redis,然而,python-redis-lock提供了一种方便的缓存后端,它为您提供了缓存方法。只需按照这种方式安装python-redis-lock
pip install "python-redis-lock[django]"
现在将以下内容放入您的设置中
CACHES = {
'default': {
'BACKEND': 'redis_lock.django_cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient'
}
}
}
此后端仅为django-redis的缓存后端添加了一个方便的.lock(name, expire=None)函数。
您可以像这样编写您的函数
from django.core.cache import cache
def function():
val = cache.get(key)
if not val:
with cache.lock(key):
val = cache.get(key)
if not val:
# DO EXPENSIVE WORK
val = ...
cache.set(key, value)
return val
故障排除
在某些情况下,锁会永远保留在Redis中(例如服务器断电/Redis或应用程序崩溃/未处理的异常)。在这种情况下,重启应用程序不会删除锁。一种解决方案是同时开启auto_renewal参数和expire,为锁设置超时时间,但让Lock()在您的应用程序代码执行期间自动重置超时时间。
# Get a lock with a 60-second lifetime but keep renewing it automatically
# to ensure the lock is held for as long as the Python process is running.
with redis_lock.Lock(conn, name='my-lock', expire=60, auto_renewal=True):
# Do work....
另一种解决方案是在应用程序启动时使用reset_all()函数。
# On application start/restart
import redis_lock
redis_lock.reset_all()
或者,您可以通过reset方法重置单个锁。
请谨慎使用,如果您了解自己在做什么。
特性
基于标准的SETNX配方
可选过期时间
可选超时时间
可选锁续期(使用较短的过期时间但保持锁活动状态)
获取时没有自旋循环
实现
redis_lock将为每个锁使用两个键,键名为<name>
lock:<name> - 实际锁的字符串值
lock-signal:<name> - 信号列表,用于在锁释放时通知等待者
它的工作原理是这样的
文档
开发
要运行所有测试,请运行
tox
需求
- 操作系统:
任何
- 运行时间:
Python 2.7, 3.3或更高版本,或PyPy
- 服务:
Redis 2.6.12或更高版本。
类似项目
bbangert/retools - acquire会进行自旋循环
distributing-locking-python-and-redis - acquire会进行轮询
cezarsa/redis_lock - acquire不会阻塞
andymccurdy/redis-py - acquire会进行自旋循环
mpessas/python-redis-lock - 可以正常阻塞,但没有过期时间
brainix/pottery - acquire会进行自旋循环
变更日志
4.0.0 (2022-10-17)
删除了对Python 2.7和3.6的支持。
从Travis切换到GitHub Actions。
使日志消息更一致。
将redis_lock.refresh.thread.*日志记录器替换为单个redis_lock.refresh.thread日志记录器。
对各种测试进行了清理(主要删除了硬编码的临时路径)。
3.7.0 (2020-11-20)
使记录器名称更具体。现在可以对这些新记录器名称进行粒度过滤
redis_lock.acquire(发出DEBUG消息)
redis_lock.acquire(发出WARN消息)
redis_lock.acquire(发出INFO消息)
redis_lock.refresh.thread.start (发出 DEBUG 消息)
redis_lock.refresh.thread.exit (发出 DEBUG 消息)
redis_lock.refresh.start (发出 DEBUG 消息)
redis_lock.refresh.shutdown (发出 DEBUG 消息)
redis_lock.refresh.exit (发出 DEBUG 消息)
redis_lock.release (发出 DEBUG 消息)
由 Salomon Smeke Cohen 在 80 中贡献。
修复了一些关于文档检查的 CI 问题。由 Salomon Smeke Cohen 在 81 中贡献。
3.6.0 (2020-07-23)
改进了 timeout/expire 验证,以便
timeout 和 expire 如果是假值,将转换为 ``None``。之前只有 None 禁用这些选项,其他假值会创建有问题的场景。
现在允许使用大于 expire 的 timeout,如果 auto_renewal 设置为 True。之前会引发 TimeoutTooLarge 错误。见 74。
不允许使用负数的 timeout 或 expire。之前允许这样的值,并会创建有问题的场景。见 73。
更新了基准测试和示例。
移除了自定义脚本缓存代码。现在使用 redis 客户端的 register_script 方法。这在理论上将修复与 redis 集群相关的可能问题,因为 redis 客户端对此有一些特定的处理。
3.5.0 (2020-01-13)
添加了 locked 方法。由 Artem Slobodkin 在 72 中贡献。
3.4.0 (2019-12-06)
修复了可能导致某些配置中的死锁或缓慢的回归问题。见:71。
3.3.1 (2019-01-19)
修复了在运行 python-redis-lock 3.3 和 3.2 时的失败情况。见:64。
3.3.0 (2019-01-17)
修复了使用废弃的 warnings API 的问题。由 Julie MacDonell 在 54 中贡献。
在 RedisCache.lock (Django 缓存后端包装器) 中添加了 auto_renewal 选项。由 c 在 55 中贡献。
将“%(script)s not cached”的日志级别从 WARNING 更改为 INFO。
添加了对使用 decode_responses=True 的支持。锁键现在是纯 ASCII。
3.2.0 (2016-10-29)
修改了信号键清理操作,不使用任何过期时间。这可以防止某些时间后仍存在残留键。由 Andrew Pashkin 在 38 中贡献。
允许具有给定 id 的锁获取。之前它假设如果指定了 id,则锁已被获取。见 44 和 39。
允许使用具有 strict=False 的其他 redis 客户端。通常期望传入一个 redis.StrictRedis 实例。
向 Django 缓存后端添加了便利方法 locked_get_or_set。
3.1.0 (2016-04-16)
修改自动续订,如果锁被垃圾回收,则自动停止续订线程。由 Andrew Pashkin 在 33 中贡献。
3.0.0 (2016-01-16)
修改了 release,使其立即过期信号键。由 Andrew Pashkin 在 28 中贡献。
重置锁(reset 或 reset_all)将释放锁。如果有其他人正在等待重置锁,它将获取该锁。由 Andrew Pashkin 在 29 中贡献。
在 Lock 对象上添加了 extend 方法。由 Andrew Pashkin 在 24 中贡献。
在 release 方法上改进了文档。由 Andrew Pashkin 在 22 中贡献。
当使用 expire 选项时,修复了 acquire(block=True) 的处理(它不会无限期地阻塞)。由 Tero Vuotila 在 35 中贡献。
将 release 修改为检查是否使用相同 id 获取了锁。如果不是,将引发 NotAcquired。之前只是检查是否使用相同的实例(self._held)获取了锁。向后不兼容
从 release 中移除了 force 选项——它实际上并不必要,而且只会鼓励马虎编程。见 25。向后不兼容
取消了 Python 2.6 的测试。它可能可以工作,但它不受支持。
2.3.0 (2015-09-27)
添加了 timeout 选项。由 Victor Torres 在 20 中贡献。
2.2.0 (2015-08-19)
添加了 auto_renewal 选项。由 Nick Groenen 在 18 中贡献。
2.1.0 (2015-03-12)
添加了新的特定异常类: AlreadyAcquired 和 NotAcquired。
当使用非等待获取时,略微提高了效率。
2.0.0 (2014-12-29)
将 Lock.token 重命名为 Lock.id。现在只能通过构造函数设置。由 Jardel Weyrich 在 11 中贡献。
1.0.0 (2014-12-23)
修复了 Django 集成。(由 Jardel Weyrich 报告)
重新组织测试以使用 py.test。
添加了 Django 集成的测试。
添加了 reset_all 功能。由 Yokotoka 在 7 中贡献。
添加了 Lock.reset 功能。
公开了 Lock.token 属性。
0.1.2 (2013-11-05)
?
0.1.1 (2013-10-26)
?
0.1.0 (2013-10-26)
?
0.0.1 (2013-10-25)
PyPI 上的首次发布。