使几个分组的缓存键无效

Invalidating several grouped cache keys

我的模型 TicketType 有大约 500 个实例。

它每周只更改几次。

但如果它发生变化,我需要使所有使用旧 TicketTypes 的缓存值无效。

遗憾的是,一些缓存键未修复。它们包含计算数据。

我看到了这些解决方案:

使用 version 参数并在 TicketType 的 post 保存信号处理程序上更新版本值。

对基于 TicketType 的所有缓存键使用通用前缀。 然后使 post 保存信号处理程序中的所有缓存键无效。

我想还有第三种更好的方法...

示例:

TicketType 是一棵树。 TicketTypes 的可见性与权限绑定。如果两个用户具有不同的权限,他们可能会以不同的方式查看树。我们根据权限缓存它。用户的权限被序列化和散列。通过创建包含哈希和固定部分的字符串来创建缓存键:

hash_key='ticket-type-tree--%s' % hashed_permissions

如果 TicketType 树发生变化,我们需要确保不会从缓存中加载旧数据。只要不使用旧数据,就不需要主动失效。

嗯,基本上你的问题只是缓存键的表达能力。当你必须做一些像散列集合这样复杂的事情来获取密钥时,它一定是一个提示,缺少一些东西。

在你的情况下,我相信缺少的只是一个 "permission set" 对象。你可以称它为一个组,一个角色(如在 RBAC 中)......这就是为什么我问你集合是否重复 - 实际上,你的哈希键只是一种重新创建不存在的集合对象的 ID 的方法。

所以解决方案是:

  • 创建一个榜样,M2M 与用户相关,M2M 与权限相关(据我所知,这与您的 TicketType 相关联)
  • 使用事件处理程序来捕获对 TicketType 的保存。
    • 获取所有受影响的角色(通​​过权限)
    • 生成密钥(类似于 ticket-type-TREEID-ROLEID)并使其失效

最后两点:

  • 有时 cache.clear() 是解决方案 - 特别是如果您不将缓存用于其他用途时
  • 您说在树中导航时您的 SQL 查询计数很大。如果您还没有尝试过,您可能只是想通过预取和 select_related(参见文档)对其进行优化。

在 TicketType post 中保存信号处理程序:
a) 根据所有用户的权限生成密钥并使密钥无效
b)为每个排列(许可)生成密钥(如果你可以计算它们)并使密钥无效
c) 使用第二个 memcached 实例仅存储这些缓存并清除它(最简单)

P.S.: 专业提示是刷新缓存而不是仅仅使它们无效。但是,django 信号中未捕获的异常可能会很麻烦,所以要小心

您可以将工单修改时间用作缓存键的一部分。

hash_key = 'ticket-type-tree--%s-%s' % (hashed_permissions, tree.lastmodified)

您可以添加 DateTimeFieldauto_now=True。如果从数据库获取修改时间太昂贵,你也可以缓存它。

通常,在 post_save 信号处理程序中更新缓存就可以了。除非你想一直拥有一致的数据,并且想为交易支付额外的费用。

使用 redis 来缓存你的模型

我缓存实例的方式如下:

1-确保您每次都收到一件商品。例如:Model.objects.get(foo='bar'),并且您每次都使用属性 foo 从数据库中获取模型。这将用于确保数据稍后失效。

2-覆盖方法 save() 并确保它使用 foo 属性将数据保存到缓存中。

例如:

class Model(model.Model):
    foo = models.CharField()
    bar = models.CharField()

    def save(self, *args, **kwargs):
        redis.set(foo, serialize_model())
        super(Model, self).save(*args, **kwargs)

    def serialize_model():
        return serilized_object

3-覆盖 get 方法以在访问数据库之前获取序列化对象。

例如:

class Model(model.Model):
    ...
    def get(self, *args, **kwargs):
        if redis.get(self.foo):
            return redis.get(self.foo)
        else:
            return super(Model).get(*args, **kwargs)

4-如果实例被移除或删除,覆盖您的删除方法以移除缓存

例如

class Model(model.Model):
    ...
    def delete(self,*args, **kwargs):
        redis.delete(self.foo)
        super(Model, self).delete(*args, **kwargs)

将模型 class 替换为您的模型,在本例中为 Ticket Type

有一件事,我假设您不会在 Django 应用程序之外接触数据库。如果您在任何其他地方使用原始 sql,这将不起作用。

在他们的网站上找redis functions,他们有删除,设置和获取的功能。如果您使用其他缓存方式。寻找如何设置、获取和删除。