我刚试了一个Python shelve模块作为从外部服务获取的数据的持久缓存。 The complete example is here
我想知道如果我想让这个多进程安全的话,最好的方法是什么?我知道redis,memcached和这样的“真正的解决方案”,但是我只想使用Python标准库的部分或非常小的依赖性来保持我的代码紧凑,并且在单个进程中运行代码时不会引入不必要的复杂性 - 单线程模型。
很容易想出一个单进程解决方案,但这不适用于当前的Python Web运行时。具体来说,问题在于Apache + mod_wsgi环境
只有一个进程正在更新缓存数据一次(文件锁定,某种程度上?)
其他进程在更新正在进行时使用缓存数据
如果进程无法更新缓存数据,那么在另一个进程可以再次尝试(防止thundering herd等)之前会有N分钟的惩罚 - 如何在mod_wsgi进程之间发出信号
你没有使用任何“重工具”,只有Python标准库和UNIX
此外,如果某些PyPi包在没有外部依赖性的情况下执行此操作,请告诉我。欢迎使用替代方法和建议,例如“只使用sqlite”。
示例:
import datetime
import os
import shelve
import logging
logger = logging.getLogger(__name__)
class Converter:
def __init__(self, fpath):
self.last_updated = None
self.data = None
self.data = shelve.open(fpath)
if os.path.exists(fpath):
self.last_updated = datetime.datetime.fromtimestamp(os.path.getmtime(fpath))
def convert(self, source, target, amount, update=True, determiner="24h_avg"):
# Do something with cached data
pass
def is_up_to_date(self):
if not self.last_updated:
return False
return datetime.datetime.now() < self.last_updated + self.refresh_delay
def update(self):
try:
# Update data from the external server
self.last_updated = datetime.datetime.now()
self.data.sync()
except Exception as e:
logger.error("Could not refresh market data: %s %s", self.api_url, e)
logger.exception(e)
答案 0 :(得分:2)
我想说你想要使用一些现有的缓存库,我想到了dogpile.cache
,它已经有很多功能,你可以轻松插入你可能需要的后端。
dogpile.cache
文档说明如下:
这种“获取或创造”模式是“Dogpile”的全部关键 系统,它协调许多中的单个值创建操作 并发获取特定键的操作,消除了问题 许多工人多余重新生成过期值 同时进行。
答案 1 :(得分:2)
让我们系统地考虑您的要求:
您的用例将确定您是否可以使用带内(跨fork继承的文件描述符或内存区域)或带外同步(posix文件锁,sys V共享内存)。
然后您可能有其他要求,例如工具的跨平台可用性等。
除了裸工具之外,标准库中确实没有那么多。然而,一个模块突出,sqlite3
。 Sqlite使用fcntl / posix锁,但存在性能限制,多个进程意味着文件支持的数据库,而sqlite在提交时需要fdatasync。
因此,硬盘驱动器rpm强加的sqlite中的事务/ s是有限制的。如果你进行突袭,后者不是什么大问题,但可能是商品硬件的主要障碍,例如:笔记本电脑或USB闪存或SD卡。如果您使用普通的旋转硬盘,请计划~100tps。
如果您使用特殊交易模式,您的流程也可以阻止sqlite。
有两种主要方法:
如果您信任具有缓存值的其他进程,则可能没有任何安全注意事项。因此,要么两者都有效,要么两者兼而有之。
答案 2 :(得分:0)
我围绕标准shelve
模块编写了一个无外部依赖的锁定包装(线程和多进程安全):
https://github.com/cristoper/shelfcache
它可以满足您的许多要求,但是它没有任何防止退潮的退避策略,如果您想使用Reader-Writer锁(这样就可以读取多个线程,但只能写入一个),则必须提供您拥有RW锁。
但是,如果我要再做一次,我可能会“只使用sqlite”。 shelve
模块抽象了几种不同的dbm实现,它们本身又通过各种OS锁定机制抽象了(例如,在Mac OS X(或busybox)上对gdbm使用书架缓存flock
选项) ,导致出现死锁)。
有几个python项目试图为sqlite或其他持久性存储提供标准的dict接口,例如:https://github.com/RaRe-Technologies/sqlitedict
(请注意,即使对于同一数据库连接,sqldict
也是 thread 安全的,但是在进程之间共享同一数据库连接是不安全的。)