使用PostgreSQL顾问锁轻松实现分布式锁定。
PALs的Python项目详细描述
简介
pals使使用PostgreSQL Advisory Locks来做分布式应用程序级别变得很容易 锁定。
不要将这种类型的锁定与postgresql中的表或行锁定混淆。不一样了 事情。
使用redis、memcache、zeromq和 其他人。但是对于那些已经在使用postgresql的用户来说,另一个服务的设置和管理是 没必要。
用法
# Think of the Locker instance as a Lock factory.locker=pals.Locker('my-app-name','postgresql://user:pass@server/dbname')lock1=locker.lock('my-lock')lock2=locker.lock('my-lock')# The first acquire worksassertlock1.acquire()isTrue# Non blocking version should fail immediatelyassertlock2.acquire(blocking=False)isFalse# Blocking version will retry and eventually failacquired,retries=lock2.acquire(return_retries=True)assertacquiredisFalseassertretries>4# You can set the retry parameters yourself if you don't like our defaults.lock2.acquire(retry_delay=100,retry_timeout=300)# They can also be set on the lock instancelock3=locker.lock('my-lock',retry_delay=100,retry_timeout=300)# Release the locklock1.release()# Recommended usage pattern:ifnotlock1.acquire():# Remember to check to make sure you got your lockreturntry:# do my work herefinally:lock1.release()# But more recommended and easier is to use the lock as a context manager:withlock1:assertlock2.acquire()isFalse# Outside the context manager the lock should have been released and we can get it nowassertlock2.acquire()# The context manager version will throw an exception if it fails to acquire the lock. This# pattern was chosen because it feels symantically wrong to have to check to see if the lock# was actually acquired inside the context manager. If the code inside is ran, the lock was# acquired.try:withlock1:# We won't get here because lock2 acquires the lock just abovepassexceptpals.AcquireFailure:pass
在本地运行测试
设置数据库连接
我们提供了docker compose文件,但您不必使用它:
$ docker-compose up -d $ export PALS_DB_URL=postgresql://postgres:password@localhost:54321/postgres
您还可以将环境变量放在.env文件中,pipenv将接收它。
运行测试
含毒物:
$ tox
或者,手动:
$ pipenv install --dev $ pipenv shell $ pytest pals/tests.py
解除锁定和过期
与建立在memcache和redis等缓存服务上的锁定系统不同,它们的密钥可以过期 通过该服务,postgresql中没有过期咨询锁的能力。如果客户 持有一个锁,然后睡眠/挂起分钟/小时/天,没有其他客户能够得到 锁定,直到客户端释放它。这对我们来说是件好事,如果锁是 获得后,应该一直保存到释放。
但是,万一锁不开怎么办?
- 如果开发人员使用
lock.acquire()
,但以后不调用lock.release()
? - 如果锁中的代码意外抛出异常(并且.release()未被调用)?
- 如果运行应用程序的进程崩溃或进程的服务器死机?
好友以几种不同的方式帮助上面的1和2:
- 锁作为上下文管理器工作。尽可能多地使用它们以确保锁被释放。
- 垃圾收集时锁会释放锁。
- pals使用专用的sqlalchemy连接池。当连接返回到池时,
要么是因为调用了连接
.close()
,要么是因为对连接进行了垃圾收集, pals发出apg_advisory_unlock_all()
。因此,一个无所事事的人是不可能的 池中的连接始终保持锁定。
关于上面的3,pg_advisory_unlock_all()
被postgresql隐式调用,只要
连接(也称为会话)结束,即使客户端断开连接不正常。所以如果一个过程
崩溃或以其他方式消失,postgresql应该注意并移除所有由它持有的锁
连接/会话。
可能存在PostgreSQL没有检测到连接已关闭并保持不变的可能性。
无限期打开的锁。然而,在使用scripts/hang.py
的手动测试中,没有找到任何方法
在postgresql没有检测到的情况下结束python进程。
另请参见
更改日志
0.2.0发布日期:2019-03-07
- 修正“acquire”(737763f)的拼写错误