(Django)实用的缓存策略和实现?缓存时间长,数据变更后使缓存失效。

7
我有一个Django应用程序,可以获取准实时数据(推文和投票),但平均每一两分钟才会更新。然而,我们希望在数据到达时立即通过更新网站和API结果来显示数据。
我们可能会看到这个网站上的大量负载,所以我的初步想法当然是缓存!
有没有一种实用的Memcached缓存方式,可以由另一个进程或事件手动失效?换句话说,我会长时间缓存视图,然后有新的推文和投票使整个视图失效。
这些或许适度的性能提升是否值得增加复杂性?
是否有一个实用的实现方法我可以创建(我与其他开发人员一起工作,因此在每个响应调用周围进行大量修改并不是一个好选择)?
我不关心仅使某些对象失效,我考虑子类化MemcachedCache后端以添加一些功能,按照this strategy的策略进行操作。但当然,Django的会话也使用Memcached作为写入缓存,我不想使其无效。
2个回答

7

缓存失效可能是处理您正在尝试完成的内容的最佳方法。根据您问题的措辞,我将假设您的应用程序具有以下特点:

  • 您已经使用某种API接收新信息更新,并且不进行轮询。例如:每一两分钟,您会收到一个API请求,并将某些信息存储在数据库中。
  • 您已经在使用Memcached来缓存阅读材料。可能通过cronjob或类似进程定期扫描数据库并更新缓存。

假设上述两个条件为真,则缓存失效绝对是正确的选择。以下是在Django中实现它的最佳方法:

  1. 新的API请求进入服务器,其中包含要存储的新数据。您将其保存在数据库中,并在您的模型类(例如Tweet、Poll等)上使用post save signal更新您的memcached数据。
  2. 用户访问您的网站并请求阅读他们最近的推文、投票或其他内容。
  3. 您从memcached中获取推文、投票等数据,并向他们展示。
这就是Django信号的基本用途。它们会在对象保存/更新后自动运行,这是更新缓存存储以获取最新信息的绝佳时机。
以这种方式完成意味着您永远不需要运行定期扫描数据库并更新缓存的后台作业 - 您的缓存将始终立即使用最新数据更新。

4

多亏了@rdegges的建议,我能够找到一个很好的方法来实现这个。

我遵循这种模式:

  • 将渲染的模板片段和API调用缓存五分钟(或更长时间)
  • 每次添加新数据时使缓存失效。
    • 仅使缓存失效比在保存时重新缓存更好,因为当没有找到缓存数据时,新的缓存数据会自动、有机地生成。
  • 在进行完整更新后(例如从推特搜索),手动使缓存失效,而不是在每个对象保存时。
    • 这样做的好处是缓存失效的次数较少,但缺点是不够自动化。

以下是实现此方式所需的所有代码:

from django.conf import settings
from django.core.cache import get_cache
from django.core.cache.backends.memcached import MemcachedCache
from django.utils.encoding import smart_str
from time import time

class NamespacedMemcachedCache(MemcachedCache):

    def __init__(self, *args, **kwargs):
        super(NamespacedMemcachedCache, self).__init__(*args, **kwargs)
        self.cache = get_cache(getattr(settings, 'REGULAR_CACHE', 'regular'))
        self.reset()

    def reset(self):
        namespace = str(time()).replace('.', '')
        self.cache.set('namespaced_cache_namespace', namespace, 0)
        # note that (very important) we are setting
        # this in the non namespaced cache, not our cache.
        # otherwise stuff would get crazy.
        return namespace

    def make_key(self, key, version=None):
        """Constructs the key used by all other methods. By default it
        uses the key_func to generate a key (which, by default,
        prepends the `key_prefix' and 'version'). An different key
        function can be provided at the time of cache construction;
        alternatively, you can subclass the cache backend to provide
        custom key making behavior.
        """
        if version is None:
            version = self.version

        namespace = self.cache.get('namespaced_cache_namespace')
        if not namespace:
            namespace = self.reset()
        return ':'.join([self.key_prefix, str(version), namespace, smart_str(key)])

这个方法是通过在每个缓存条目上设置一个版本或命名空间,并将该版本存储在缓存中来实现的。当调用reset()时,版本只是当前的时代时间。
您必须使用settings.REGULAR_CACHE指定您的备用非命名空间缓存,以便版本号可以存储在非命名空间缓存中(这样就不会递归!)。
每当您添加一堆数据并想要清除缓存(假设您已将this设置为default缓存),只需执行以下操作:
from django.core.cache import cache
cache.clear()

您可以使用以下方式访问任何缓存:
from django.core.cache import get_cache
some_cache = get_cache('some_cache_key')

最后,我建议您不要将您的会话放入此缓存中。 您可以使用此方法更改会话的缓存键。(如settings.SESSION_CACHE_ALIAS)。

1
只是附带一提,我现在仍然使用这种方法,但是改用了Redis。我创建了一个后保存信号接收器,每次保存任何内容时都会使整个缓存失效(这有点过度杀伤力,但非常简单)。 - Dave

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接