Python中具有stdlib或最小依赖项的持久多进程共享缓存
我刚刚尝试使用Python模块作为从外部服务获取的数据的持久缓存 我想知道如果我想让这个多进程安全,最好的方法是什么?我知道redis、memcached等“真正的解决方案”,但我只想使用Python标准库的部分或非常小的依赖项来保持代码紧凑,并且在单进程-单线程模型中运行代码时不会引入不必要的复杂性 很容易想出一个单一的进程解决方案,但在当前的Python web运行时,这一点并不奏效。具体来说,问题是在Apache+mod_wsgi环境中Python中具有stdlib或最小依赖项的持久多进程共享缓存,python,memcached,multiprocessing,mod-wsgi,shelve,Python,Memcached,Multiprocessing,Mod Wsgi,Shelve,我刚刚尝试使用Python模块作为从外部服务获取的数据的持久缓存 我想知道如果我想让这个多进程安全,最好的方法是什么?我知道redis、memcached等“真正的解决方案”,但我只想使用Python标准库的部分或非常小的依赖项来保持代码紧凑,并且在单进程-单线程模型中运行代码时不会引入不必要的复杂性 很容易想出一个单一的进程解决方案,但在当前的Python web运行时,这一点并不奏效。具体来说,问题是在Apache+mod_wsgi环境中 只有一个进程更新缓存的数据一次(不知何故,文件锁定
- 只有一个进程更新缓存的数据一次(不知何故,文件锁定?)
- 其他进程在更新过程中使用缓存的数据
- 如果进程未能更新缓存的数据,那么在另一个进程重试之前(为了防止这样的情况),将有N分钟的惩罚-如何在mod_wsgi进程之间发出此信号
- 您不使用任何“繁重的工具”,只使用Python标准库和UNIX
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)
导入日期时间
导入操作系统
进口货架
导入日志记录
logger=logging.getLogger(_名称__)
类别转换器:
定义初始化(self,fpath):
self.last_updated=无
self.data=None
self.data=shelve.open(fpath)
如果os.path.exists(fpath):
self.last_updated=datetime.datetime.fromtimestamp(os.path.getmtime(fpath))
def转换(自身、来源、目标、金额、更新=True,determinator=“24小时平均值”):
#处理缓存数据
通过
def是最新的(自我):
如果未自我更新,则上次更新:
返回错误
return datetime.datetime.now()
我想说的是,您可能想使用一些现有的缓存库,想到这里,它已经有很多功能,您可以轻松地插入可能需要的后端
dogpile.cache
文档说明了以下内容:
这种“获取或创建”模式是“狗堆”的全部关键
系统,协调多个系统之间的单个价值创造操作
针对特定密钥的并发get操作,消除了该问题
由许多工作人员冗余地重新生成过期值
同时
让我们系统地考虑你的要求: 最小或无外部依赖关系 您的用例将决定您是否可以使用带内(文件描述符或跨fork继承的内存区域)或带外同步(posix文件锁、SYSV共享内存) 然后您可能有其他要求,例如工具的跨平台可用性等 标准库中真的没有那么多,除了裸工具。然而,有一个模块很突出,
sqlite3
。Sqlite使用fcntl/posix锁,但存在性能限制,多个进程意味着有文件支持的数据库,并且Sqlite在提交时需要fdatasync
因此,您的硬盘rpm对sqlite中的事务/s有一个限制。如果您有硬件raid,后者不是什么大问题,但可能是商品硬件的主要障碍,例如笔记本电脑、usb闪存或sd卡。如果您使用的是常规的旋转硬盘驱动器,请计划约100tps
如果您使用特殊的事务模式,您的进程也可以在sqlite上阻塞
防雷鸣群
为此,有两种主要方法:
- 可能在要求之前刷新缓存项,或
- 仅在需要时刷新,但阻止其他呼叫者
搁置
模块周围编写了一个锁定(线程和多进程安全)包装,没有外部依赖:
它满足您的许多需求,但它没有任何类型的回退策略来防止雷鸣般的群体,如果您想要读写器锁(这样多个线程可以读取,但只有一个写入),您必须提供自己的RW锁
然而,如果我再做一次,我可能会“只使用sqlite”。shelve
模块在几个不同的dbm实现上进行抽象,而这些dbm实现本身在各种操作系统锁定机制上进行抽象,这是一个难题(例如,在Mac OS X(或busybox)上使用带有gdbm的shelfcacheflock
选项会导致死锁)
有几个python项目试图为sqlite或其他持久存储提供标准dict接口,例如:
(注意,
sqldict
即使对于相同的数据库连接也是线程安全的,但是在进程之间共享相同的数据库连接是不安全的。)+1对于dogpile,它非常好,可以处理轰鸣的hurd。这是一个第三方软件包,在实际使用中可能需要一些依赖项,因此与OP有点不协调。我目前正在研究同样的任务,也遇到了dogpile。虽然我还没有完全理解dogpile的内部结构,但是在文档中有很多使用线程的情况。根据[deadlock]()和其他人,包括我的经验,多处理+多线程+日志记录可能会导致死锁。这可以通过以下方式避免: