Python中具有stdlib或最小依赖项的持久多进程共享缓存

Python中具有stdlib或最小依赖项的持久多进程共享缓存,python,memcached,multiprocessing,mod-wsgi,shelve,Python,Memcached,Multiprocessing,Mod Wsgi,Shelve,我刚刚尝试使用Python模块作为从外部服务获取的数据的持久缓存 我想知道如果我想让这个多进程安全,最好的方法是什么?我知道redis、memcached等“真正的解决方案”,但我只想使用Python标准库的部分或非常小的依赖项来保持代码紧凑,并且在单进程-单线程模型中运行代码时不会引入不必要的复杂性 很容易想出一个单一的进程解决方案,但在当前的Python web运行时,这一点并不奏效。具体来说,问题是在Apache+mod_wsgi环境中 只有一个进程更新缓存的数据一次(不知何故,文件锁定

我刚刚尝试使用Python模块作为从外部服务获取的数据的持久缓存

我想知道如果我想让这个多进程安全,最好的方法是什么?我知道redis、memcached等“真正的解决方案”,但我只想使用Python标准库的部分或非常小的依赖项来保持代码紧凑,并且在单进程-单线程模型中运行代码时不会引入不必要的复杂性

很容易想出一个单一的进程解决方案,但在当前的Python web运行时,这一点并不奏效。具体来说,问题是在Apache+mod_wsgi环境中

  • 只有一个进程更新缓存的数据一次(不知何故,文件锁定?)

  • 其他进程在更新过程中使用缓存的数据

  • 如果进程未能更新缓存的数据,那么在另一个进程重试之前(为了防止这样的情况),将有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)
导入日期时间
导入操作系统
进口货架
导入日志记录
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的shelfcache
flock
选项会导致死锁)

有几个python项目试图为sqlite或其他持久存储提供标准dict接口,例如:


(注意,
sqldict
即使对于相同的数据库连接也是线程安全的,但是在进程之间共享相同的数据库连接是不安全的。)

+1对于dogpile,它非常好,可以处理轰鸣的hurd。这是一个第三方软件包,在实际使用中可能需要一些依赖项,因此与OP有点不协调。我目前正在研究同样的任务,也遇到了dogpile。虽然我还没有完全理解dogpile的内部结构,但是在文档中有很多使用线程的情况。根据[deadlock]()和其他人,包括我的经验,多处理+多线程+日志记录可能会导致死锁。这可以通过以下方式避免: