如何在Django中使视图缓存失效?
@cache_page 装饰器
真是太棒了。不过对于我的博客,我想在有人评论帖子之前,把页面一直保存在缓存里。这听起来是个好主意,因为人们很少评论,所以在没有人评论的时候,把页面保存在内存缓存中会很好。我在想,应该有人遇到过这个问题吧?而且这和按网址缓存是不同的。
所以我想到的解决方案是:
@cache_page( 60 * 15, "blog" );
def blog( request ) ...
然后我会保持一个博客视图中所有缓存键的列表,并且有办法让“博客”缓存空间过期。但是我对Django不是很熟悉,所以我想知道有没有人知道更好的方法来实现这个?
16 个回答
我写了一个叫做 Django-groupcache 的工具,专门用来处理这种情况(你可以在这里 下载代码)。在你的情况下,你可以这样写:
from groupcache.decorators import cache_tagged_page
@cache_tagged_page("blog", 60 * 15)
def blog(request):
...
接下来,你可以简单地这样做:
from groupcache.utils import uncache_from_tag
# Uncache all view responses tagged as "blog"
uncache_from_tag("blog")
另外,看看 cache_page_against_model() 这个函数,它稍微复杂一点,但可以让你根据模型实体的变化自动清除缓存的响应。
cache_page 装饰器最终会使用 CacheMiddleware,这个中间件会根据请求生成一个缓存键(可以查看 django.utils.cache.get_cache_key
),还有一个键前缀(在你的例子中是“blog”)。需要注意的是,“blog”只是一个前缀,并不是整个缓存键。
当评论被保存时,你可以通过 Django 的 post_save 信号 接收到通知,这样你就可以尝试为相应的页面构建缓存键,最后执行 cache.delete(key)
来删除缓存。
不过,这需要缓存键,而这个键是通过之前缓存视图的请求构建的。当评论被保存时,请求对象是不可用的。你可以在没有正确请求对象的情况下构建缓存键,但这个构建过程是在一个标记为私有的函数中(_generate_cache_header_key
),所以不应该直接使用这个函数。不过,你可以构建一个具有与原始缓存视图相同路径属性的对象,Django 不会察觉,但我不推荐这样做。
cache_page 装饰器为你抽象了缓存的过程,这使得直接删除某个缓存对象变得困难。你可以自己创建缓存键并以相同的方式处理它们,但这需要更多的编程,并不像 cache_page
装饰器那样简单。
当你的评论在多个视图中显示时(比如带有评论计数的首页和单独的博客文章页面),你还需要删除多个缓存对象。
总结一下:Django 会为你处理基于时间的缓存键过期,但在正确的时机自定义删除缓存键则更复杂。
这个解决方案适用于1.7之前的django版本
这是我为我的一些项目写的解决方案,正好可以解决你提到的问题:
def expire_view_cache(view_name, args=[], namespace=None, key_prefix=None):
"""
This function allows you to invalidate any view-level cache.
view_name: view function you wish to invalidate or it's named url pattern
args: any arguments passed to the view function
namepace: optioal, if an application namespace is needed
key prefix: for the @cache_page decorator for the function (if any)
"""
from django.core.urlresolvers import reverse
from django.http import HttpRequest
from django.utils.cache import get_cache_key
from django.core.cache import cache
# create a fake request object
request = HttpRequest()
# Loookup the request path:
if namespace:
view_name = namespace + ":" + view_name
request.path = reverse(view_name, args=args)
# get cache key, expire if the cached item exists:
key = get_cache_key(request, key_prefix=key_prefix)
if key:
if cache.get(key):
# Delete the cache entry.
#
# Note that there is a possible race condition here, as another
# process / thread may have refreshed the cache between
# the call to cache.get() above, and the cache.set(key, None)
# below. This may lead to unexpected performance problems under
# severe load.
cache.set(key, None, 0)
return True
return False
Django会把视图请求的缓存进行标记,所以这个方法是创建一个假的请求对象,用来获取缓存的键,然后让它失效。
如果你想按照你说的方式使用,可以试试这样的代码:
from django.db.models.signals import post_save
from blog.models import Entry
def invalidate_blog_index(sender, **kwargs):
expire_view_cache("blog")
post_save.connect(invalidate_portfolio_index, sender=Entry)
简单来说,每当一个博客条目对象被保存时,就会调用invalidate_blog_index,让缓存的视图失效。注意:我没有进行过大量测试,但到目前为止对我来说都很好用。