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

问题描述 投票:0回答:3

我刚刚尝试使用 Python shelve 模块作为从外部服务获取的数据的持久缓存。 完整的示例在这里

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

提出单进程解决方案很容易,但这在当前的 Python Web 运行时效果不佳。具体来说,问题是在 Apache + mod_wsgi 环境中

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

  • 其他进程在更新期间使用缓存的数据

  • 如果进程无法更新缓存数据,则在另一个进程重试之前会受到 N 分钟的惩罚(以防止 thundering herd 等) - 如何在 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)
python memcached multiprocessing mod-wsgi shelve
3个回答
4
投票

我想说你想使用一些现有的缓存库,

dogpile.cache
浮现在脑海中,它已经有很多功能,你可以轻松插入你可能需要的后端。

dogpile.cache
文档说明了以下内容:

这种“获取或创建”模式是“Dogpile”的全部关键 系统,协调多个系统之间的单一价值创造操作 针对特定键的并发获取操作,消除了该问题 过期值被许多工人多余地重新生成 同时。


3
投票

让我们系统地考虑您的需求:

最少或没有外部依赖

您的用例将决定您是否可以使用带内(跨 fork 继承的文件描述符或内存区域)或带外同步(posix 文件锁、sys V 共享内存)。

那么您可能还有其他要求,例如工具的跨平台可用性等

除了裸露的工具之外,标准库中确实没有那么多东西。然而,有一个模块很突出,

sqlite3
。 Sqlite 使用 fcntl/posix 锁,但存在性能限制,多个进程意味着文件支持的数据库,并且 sqlite 在提交时需要 fdatasync。

因此,sqlite 中的事务数受到硬盘转速的限制。如果您有硬件突袭,后者并不是什么大问题,但可能会成为商品硬件的主要障碍,例如笔记本电脑、USB 闪存或 SD 卡。如果您使用常规旋转硬盘,请计划 ~100tps。

如果您使用特殊的事务模式,您的进程也可能会在 sqlite 上阻塞。

防止雷群

有两种主要方法:

  • 有可能早于所需时间刷新缓存项,或者
  • 仅在需要时刷新,但阻止其他调用者

如果您信任另一个进程拥有缓存值,那么您就没有任何安全考虑。因此,两者都可以,或者两者兼而有之。


1
投票

我围绕标准

shelve
模块编写了一个锁定(线程和多进程安全)包装器,没有外部依赖项:

https://github.com/cristoper/shelfcache

它满足您的许多要求,但它没有任何类型的退避策略来防止惊群,如果您想要读写锁(以便多个线程可以读取,但只能写入一个),您必须提供自己的RW 锁。

但是,如果我再做一次,我可能会“只使用 sqlite”。

shelve
模块抽象了几种不同的 dbm 实现,而这些实现本身又抽象了各种操作系统锁定机制,这是一种痛苦(例如,在 Mac OS X(或 busybox)上使用带有 gdbm 的 shelfcache
flock
选项,会导致僵局)。

有几个Python项目试图为sqlite或其他持久存储提供标准的dict接口,例如:https://github.com/RaRe-Technologies/sqlitedict

(请注意,即使对于相同的数据库连接,

sqldict
也是线程安全的,但在进程之间共享相同的数据库连接是不安全的。)

© www.soinside.com 2019 - 2024. All rights reserved.