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

时间:2013-12-06 09:18:26

标签: python memcached multiprocessing mod-wsgi shelve

我刚试了一个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)

3 个答案:

答案 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 安全的,但是在进程之间共享同一数据库连接是不安全的。)