【问题标题】:Invalidating several grouped cache keys使多个分组缓存键失效
【发布时间】:2015-05-07 11:50:56
【问题描述】:

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

每周只更换几次。

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

很遗憾,有些缓存键没有固定。它们包含计算数据。

我看到了这些解决方案:

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

为所有基于 TicketType 的缓存键使用一个公共前缀。 然后在后保存信号处理程序中使所有缓存键无效。

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

例子:

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

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

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

【问题讨论】:

  • 能否请您详细说明一下您的模型、缓存的内容以及您确定缓存键的方式?
  • @Marcanpilami 我更新了问题。
  • 能否请您展示您的 TicketType 模型以及您明确放入缓存中的内容?
  • 还有多少个用户?您是否在密钥中使用哈希,因为权限集是重复的并且树很大?
  • 是的,权限集是重复的。这些树并不大,但计算它们需要一些时间。对于树中的每个节点,都会执行一个 SQL 计数查询。计数很慢。这就是我们进行缓存的原因。

标签: django caching


【解决方案1】:

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

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

您可以使用auto_now=True 添加DateTimeField。如果从 db 获取修改时间太昂贵,你也可以缓存它。

通常,在post_save 信号处理程序中更新缓存是可以的。除非您希望始终拥有一致的数据并希望为交易支付额外费用。

【讨论】:

  • 因为树不经常改变。我更喜欢 one 时间戳。如果我添加一个额外的DateTimeField,每一行都会有一个值。获取本专栏的最后修改将很容易 (max())。我想我使用这样的版本,但不是last_modified,我将采用存储在缓存中的version(没有超时)并使用incr()
  • 是的,您可以使用任何您知道每次修改都会更改的字段。
【解决方案2】:

使用 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)

用你的模型替换 Model 类,在这种情况下它是 Ticket Type

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

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

【讨论】:

  • 这个缓存太多了。就我而言,我想缓存树结构,而不是所有单个对象。答案不适合我的用例。对不起。
【解决方案3】:

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

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

所以解决办法是:

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

最后两句:

  • 有时 cache.clear() 是解决方案 - 特别是如果您不将缓存用于其他任何事情
  • 您说在导航树时您的 SQL 查询计数很大。如果您还没有尝试过,您可能只是想使用 prefetch 和 select_related 来优化它(参见文档)。

【讨论】:

  • 我认为修改数据库模式来解决这个问题并不是很好的解决方案。这将是多余的,这是我在数据库模式中避免的。 cache.clear() 不是解决方案。缓存也用于许多不同的事情。 SQL 查询数量并不多。 SQL (postgres) 中的计数很慢。这就是为什么 select_related() 或 prefetch_related() 没有帮助的原因。
【解决方案4】:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-03
    • 2017-09-08
    • 1970-01-01
    • 2011-01-13
    • 1970-01-01
    • 2018-12-28
    • 2015-04-29
    • 2019-02-22
    相关资源
    最近更新 更多