【问题标题】:Persistent multiprocess shared cache in Python with stdlib or minimal dependenciesPython 中具有 stdlib 或最小依赖项的持久多进程共享缓存
【发布时间】:2013-12-06 09:18:25
【问题描述】:

我刚刚尝试使用 Python shelve 模块作为从外部服务获取的数据的持久缓存。 The complete example is here.

我想知道如果我想让这个多进程安全,最好的方法是什么?我知道 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


【解决方案1】:

我想说你想使用一些现有的缓存库,我想到了dogpile.cache,它已经有很多功能,你可以轻松插入你可能需要的后端。

dogpile.cache 文档说明如下:

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

【讨论】:

  • +1 for dogpile,很不错,可以应对雷声。它是第 3 方包,在实际使用中可能需要一些依赖项,因此与 OP 有点格格不入。
  • 我目前正在研究同样的任务,并且也遇到了 dogpile。虽然我还没有完全理解 dogpile 的内部结构,但文档中有多次使用线程。根据 [deadlock] (stackoverflow.com/questions/24509650/…) 和其他人,包括我的经验,多处理 + 多线程 + 日志记录会导致死锁。这可以通过首先产生进程然后再产生线程来避免。
  • @ZoltanK。我把你推荐给我的另一个答案stackoverflow.com/a/46440564/918959 :)
  • 另外,dogpile.cache 本身是无线程的,它只使用threading.Lock
【解决方案2】:

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

最少或没有外部依赖

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

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

标准库中真的没有那么多,除了裸工具。然而,一个模块脱颖而出,sqlite3。 SQLite 使用 fcntl/posix 锁,但存在性能限制,多进程意味着文件支持数据库,并且 sqlite 在提交时需要 fdatasync。

因此,您的硬盘驱动器 rpm 对 sqlite 中的事务/秒有限制。如果您有硬件突袭,后者并不是什么大问题,但可能是商品硬件的主要障碍,例如笔记本电脑或 USB 闪存或 SD 卡。如果您使用普通的旋转硬盘,请计划约 100 tps。

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

防雷击群

有两种主要的方法:

  • 在需要之前以概率方式刷新缓存项,或者
  • 仅在需要时刷新,但阻止其他调用者

大概如果您使用缓存值信任另一个进程,则您没有任何安全考虑。因此,任何一个都可以工作,或者两者兼而有之。

【讨论】:

  • 酷,这是我一直在寻找的高质量输入。
  • 写。事务/秒限制:如果您在重启时不需要一致的缓存,用户PRAGMA synchronous = OFF,那么 fdatasyncs 没有完成并且性能再次很酷;确保在启动时清除缓存。或者将数据库保存在 tmpfs/ramfs 卷上。
【解决方案3】:

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

https://github.com/cristoper/shelfcache

它满足您的许多要求,但它没有任何形式的退避策略来防止雷声,如果您想要 Reader-Writer 锁(以便多个线程可以读取,但只能写入一次),您必须提供你拥有 RW 锁。

但是,如果我要再做一次,我可能会“只使用 sqlite”。 shelve 模块抽象了几个不同的 dbm 实现,这些实现本身抽象了各种 OS 锁定机制,这很痛苦(例如,在 Mac OS X(或busybox)上使用带有 gdbm 的shelfcache flock 选项会导致死锁)。

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-02-18
    • 2014-01-19
    • 1970-01-01
    • 2023-02-24
    • 1970-01-01
    • 2011-11-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多