【问题标题】:Invalidating a path from the Django cache recursively递归地使 Django 缓存中的路径无效
【发布时间】:2010-12-31 23:52:30
【问题描述】:

我正在从 Django 缓存中删除一条路径,如下所示:

from models                   import Graph
from django.http              import HttpRequest
from django.utils.cache       import get_cache_key
from django.db.models.signals import post_save
from django.core.cache        import cache

def expire_page(path):
    request      = HttpRequest()
    request.path = path
    key          = get_cache_key(request)
    if cache.has_key(key):   
        cache.delete(key)

def invalidate_cache(sender, instance, **kwargs):
    expire_page(instance.get_absolute_url())

post_save.connect(invalidate_cache, sender = Graph)

这可行 - 但有没有办法递归删除?我的路径如下所示:

/graph/123
/graph/123/2009-08-01/2009-10-21

每当保存 id 为“123”的图形时,两条路径的缓存都需要失效。这个可以吗?

【问题讨论】:

  • 我不确定如果我理解你的问题是否正确,你的意思是你想刷新所有缓存,除了一个 id 为“123”的缓存吗?
  • 我想刷新任何以'/graph/123/'开头的路径的缓存。
  • 我不明白你为什么担心路径?
  • 因为 Django 使用请求路径为缓存创建一个键。您认为不清楚的地方是什么?

标签: django caching path invalidation


【解决方案1】:

您可能想要考虑采用分代缓存策略,它似乎适合您想要完成的任务。在您提供的代码中,您将为每个绝对 url 存储一个“世代”编号。因此,例如,您将初始化“/graph/123”以生成一代,然后其缓存键将变为类似于“/GENERATION/1/graph/123”的内容。当您想要使该绝对 url 的缓存过期时,您增加其生成值(在这种情况下为 2)。这样,下次有人去查找“/graph/123”时,缓存键就变成了“/GENERATION/2/graph/123”。这也解决了所有子页面过期的问题,因为它们应该引用与“/graph/123”相同的缓存键。

起初理解起来有点棘手,但它是一种非常优雅的缓存策略,如果正确完成意味着您永远不必从缓存中实际删除任何内容。如需更多信息,请访问a presentation on generational caching,它适用于 Rails,但无论语言如何,概念都是相同的。

【讨论】:

  • 所以您是说/graph/123 的缓存键将始终存在并包含内容/gen/1/graph/123 或/gen/2/graph/123 等...所以应用程序首先查询/graph/123 以获取最近一代key的key,然后用/gen/x/graph/123再次查询缓存,得到最新的缓存内容?这就是棘手的部分,因为如果不是这种情况,如果缓存键总是随着新一代而变化,应用程序如何查询缓存。
【解决方案2】:

另一种选择是使用支持标记键和按标记逐出键的缓存。 Django 的内置缓存 API 不支持这种方法。但至少有一个缓存后端(不是 Django 本身的一部分)确实支持。

DiskCache* 是一个 Apache2 许可的磁盘和文件支持的缓存库,用纯 Python 编写,与 Django 兼容。要在您的项目中使用 DiskCache,只需安装它并配置您的 CACHES 设置。

使用pip 可以轻松安装:

$ pip install diskcache

然后配置您的CACHES 设置:

CACHES = {
    'default': {
        'BACKEND': 'diskcache.DjangoCache',
        'LOCATION': '/tmp/path/to/directory/',
    }
}

缓存set 方法由可选的tag 关键字参数扩展,如下所示:

from django.core.cache import cache

cache.set('/graph/123', value, tag='/graph/123')
cache.set('/graph/123/2009-08-01/2009-10-21', other_value, tag='/graph/123')

diskcache.DjangoCache 在内部使用 diskcache.FanoutCache。对应的 FanoutCache 可通过_cache 属性访问,并公开evict 方法。简单地驱逐所有带有/graph/123 标记的键:

cache._cache.evict('/graph/123')

虽然访问带下划线前缀的属性可能会让人觉得尴尬,但 DiskCache 项目是稳定的,不太可能对 DjangoCache 实现进行重大更改。

Django cache benchmarks 页面讨论了替代缓存后端。

  • 免责声明:我是 DiskCache 项目的原作者。

【讨论】:

    【解决方案3】:

    结帐shutils.rmtree() 或os.removedirs()。我认为第一个可能是您想要的。

    基于多个 cmets 的更新:实际上,Django 缓存机制比仅使用 path 作为密钥更通用和更细粒度(尽管您可以在该级别使用它)。我们有一些页面有 7 或 8 个单独缓存的子组件,这些子组件根据一系列标准过期。我们的组件缓存名称反映了关键对象(或对象类),并用于识别在某些更新中需要使哪些内容无效。

    我们所有的页面都有一个基于成员/非成员状态的整体缓存键,但这仅占页面的 95% 左右。其他 5% 可以根据每个成员进行更改,因此根本不会缓存。

    您如何遍历缓存以查找无效项目取决于其实际存储方式。如果是文件,您可以简单地使用 glob 和/或递归目录删除,如果是其他机制,那么您将不得不使用其他机制。

    我的回答以及其他一些 cmets 试图说的是,您如何完成缓存失效与您使用/存储缓存的方式密切相关。

    第二次更新:@andybak:所以我猜你的评论意味着我所有的商业 Django 网站都会在火焰中爆炸?感谢您对此的关注。我注意到您没有尝试解决问题。

    Knipknap 的问题在于,他有一组缓存项,看起来因为它们的名称而在层次结构中是相关的,但是缓存机制的密钥生成逻辑通过创建路径的 MD5 哈希值 + vary_on。由于没有原始路径/参数的痕迹,您将不得不详尽地猜测所有可能的路径/参数组合,希望您能找到正确的组。我还有其他更有趣的爱好。

    如果您希望能够根据路径和/或参数值的某种组合找到缓存项组,您必须使用可以直接进行模式匹配的缓存键或 某些系统会保留此信息以供搜索时使用。

    因为我们有与 OP 问题不相关的需求,所以我们在 2 年前就控制了模板片段缓存,特别是密钥生成。它允许我们以多种方式使用正则表达式来有效地使相关缓存项组无效。我们还在settings.py 中添加了可配置的默认超时和variable_on 变量名称(在运行时解决),更改了名称和超时的顺序,因为总是必须覆盖默认超时以命名片段是没有意义的,使fragment_name 可解析(即它可以是一个变量)以更好地与多级模板继承方案以及其他一些东西一起工作。

    我最初回答的唯一原因(这对于当前的 Django 确实是错误的)是因为我一直在使用更健全的缓存键,我真的忘记了我们离开的简单机制。

    【讨论】:

    • 不是我的意思。不过还是谢谢。
    • 好吧,那么你需要更具体一些。你的缓存在哪里?文件?内存缓存?数据库?如果它们在文件中,那么我的答案是正确的。如果它们在其他地方,那么您需要某种机制来匹配包含您修改的对象的所有缓存项。我们通过实现自己的缓存机制解决了这个问题(以及其他几个缓存失效问题)。做起来并不难。
    • 很抱歉投反对票,但最初的问题对于任何使用 Django 的人来说都是非常清楚的!
    猜你喜欢
    • 2018-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-30
    • 1970-01-01
    相关资源
    最近更新 更多