Django 中的视图缓存是否过期?

2025-02-27 09:06:00
admin
原创
57
摘要:问题描述:这@cache_page decorator很棒。但是对于我的博客,我想将页面保留在缓存中,直到有人对帖子发表评论。这听起来是个好主意,因为人们很少发表评论,所以在没有人发表评论时将页面保留在 memcached 中会很棒。我在想以前一定有人遇到过这个问题?这与按 URL 缓存不同。所以我想到的一个...

问题描述:

@cache_page decorator很棒。但是对于我的博客,我想将页面保留在缓存中,直到有人对帖子发表评论。这听起来是个好主意,因为人们很少发表评论,所以在没有人发表评论时将页面保留在 memcached 中会很棒。我在想以前一定有人遇到过这个问题?这与按 URL 缓存不同。

所以我想到的一个解决方案是:

@cache_page( 60 * 15, "blog" );
def blog( request ) ...

然后我会保留用于博客视图的所有缓存键的列表,然后让“博客”缓存空间过期。但我对 Django 不是很熟悉,所以我想知道是否有人知道更好的方法?


解决方案 1:

此解决方案适用于 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)

因此基本上,每当保存博客 Entry 对象时,就会调用 invalidate_blog_index,并且缓存的视图将过期。注意:尚未对此进行广泛测试,但到目前为止,它对我来说效果很好。

解决方案 2:

cache_page 装饰器最终将使用 CacheMiddleware,它将根据请求(查看django.utils.cache.get_cache_key)和 key_prefix(在本例中为“blog”)生成缓存键。请注意,“blog”只是一个前缀,而不是整个缓存键。

当评论被保存时,您可以通过django 的 post_save 信号收到通知,然后您可以尝试为适当的页面构建缓存键,最后说cache.delete(key)

但是这需要 cache_key,它是使用先前缓存的视图的请求构造的。保存评论时,此请求对象不可用。您可以在没有正确请求对象的情况下构造缓存键,但此构造发生在标记为 private ( _generate_cache_header_key) 的函数中,因此您不应该直接使用此函数。但是,您可以构建一个具有与原始缓存视图相同的路径属性的对象,而 Django 不会注意到,但我不建议这样做。

cache_page 装饰器为您抽象了相当多的缓存,使得直接删除某个缓存对象变得困难。您可以创建自己的键并以相同的方式处理它们,但这需要更多的编程,并且不像装饰器那样抽象cache_page

当您的评论显示在多个视图中(即带有评论计数的索引页和单独的博客条目页)时,您还必须删除多个缓存对象。

总结一下:Django 会为您实现基于时间的缓存键过期,但是在正确的时间自定义删除缓存键则比较棘手。

解决方案 3:

我为这种情况编写了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():它稍微复杂一些,但它允许您根据模型实体的改变自动取消缓存响应。

解决方案 4:

这在 django 1.7 上不起作用;正如您在此处看到的https://docs.djangoproject.com/en/dev/releases/1.7/#cache-keys-are-now-generated-from-the-request-s-absolute-url,新的缓存键是使用完整 URL 生成的,因此仅路径的虚假请求不起作用。您必须正确设置请求主机值。

fake_meta = {'HTTP_HOST':'myhost',}
request.META = fake_meta

如果您有多个域使用相同的视图,您应该在 HTTP_HOST 中循环它们,获取适当的密钥并对每个域进行清理。

解决方案 5:

使用最新版本的 Django(>=2.0),您所寻找的内容非常容易实现:

from django.utils.cache import learn_cache_key
from django.core.cache import cache
from django.views.decorators.cache import cache_page

keys = set()

@cache_page( 60 * 15, "blog" );
def blog( request ):
    response = render(request, 'template')
    keys.add(learn_cache_key(request, response)
    return response

def invalidate_cache()
    cache.delete_many(keys)

当有人通过 pre_save 信号更新博客中的帖子时,您可以将 invalidate_cache 注册为回调。

解决方案 6:

Django 视图缓存在 v1.7 及以上版本中失效。已在 Django 1.9 上测试。

def invalidate_cache(path=''):
    ''' this function uses Django's caching function get_cache_key(). Since 1.7, 
        Django has used more variables from the request object (scheme, host, 
        path, and query string) in order to create the MD5 hashed part of the
        cache_key. Additionally, Django will use your server's timezone and 
        language as properties as well. If internationalization is important to
        your application, you will most likely need to adapt this function to
        handle that appropriately.
    '''
    from django.core.cache import cache
    from django.http import HttpRequest
    from django.utils.cache import get_cache_key

    # Bootstrap request:
    #   request.path should point to the view endpoint you want to invalidate
    #   request.META must include the correct SERVER_NAME and SERVER_PORT as django uses these in order
    #   to build a MD5 hashed value for the cache_key. Similarly, we need to artificially set the 
    #   language code on the request to 'en-us' to match the initial creation of the cache_key. 
    #   YMMV regarding the language code.        
    request = HttpRequest()
    request.META = {'SERVER_NAME':'localhost','SERVER_PORT':8000}
    request.LANGUAGE_CODE = 'en-us'
    request.path = path

    try:
        cache_key = get_cache_key(request)
        if cache_key :
            if cache.has_key(cache_key):
                cache.delete(cache_key)
                return (True, 'successfully invalidated')
            else:
                return (False, 'cache_key does not exist in cache')
        else:
            raise ValueError('failed to create cache_key')
    except (ValueError, Exception) as e:            
        return (False, e)

用法:

status, message = invalidate_cache(path='/api/v1/blog/')

解决方案 7:

我遇到了同样的问题,并且我不想弄乱 HTTP_HOST,因此我创建了自己的 cache_page 装饰器:

from django.core.cache import cache


def simple_cache_page(cache_timeout):
    """
    Decorator for views that tries getting the page from the cache and
    populates the cache if the page isn't in the cache yet.

    The cache is keyed by view name and arguments.
    """
    def _dec(func):
        def _new_func(*args, **kwargs):
            key = func.__name__
            if kwargs:
                key += ':' + ':'.join([kwargs[key] for key in kwargs])

            response = cache.get(key)
            if not response:
                response = func(*args, **kwargs)
                cache.set(key, response, cache_timeout)
            return response
        return _new_func
    return _dec

要使页面缓存过期只需要调用:

cache.set('map_view:' + self.slug, None, 0)

其中 self.slug - 来自 urls.py 的参数

url(r'^map/(?P<slug>.+)$', simple_cache_page(60 * 60 * 24)(map_view), name='map'), 

Django 1.11,Python 3.4.3

解决方案 8:

我必须修改 mazelife 的解决方案才能使其正常工作:

def expire_view_cache(view_name, args=[], namespace=None, key_prefix=None, method="GET"):
    """
    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: http://stackoverflow.com/questions/2268417/expire-a-view-cache-in-django
        added: method to request to get the key generating properly
    """
    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()
    request.method = method
    # 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):
            cache.set(key, None, 0)
        return True
    return False

解决方案 9:

您可以不使用缓存页面装饰器,而是在没有评论的情况下手动缓存博客文章对象(或类似对象),然后当有第一条评论时,重新缓存博客文章对象以使其保持最新(假设该对象具有引用任何评论的属性),但随后只需让被评论的博客文章的缓存数据过期,然后就不用重新缓存了……

解决方案 10:

每次有人评论帖子时,您可能都会使用新的“key_prefix”,而不是显式缓存过期。例如,它可能是上一篇帖子评论的日期时间(您甚至可以将此值与Last-Modified标题结合起来)。

不幸的是,Django(包括cache_page())不支持动态“key_prefix”(在Django 1.9上检查),但存在解决方法。您可以实现自己的cache_page()扩展,CacheMiddleware其中包含动态“key_prefix”支持。例如:

from django.middleware.cache import CacheMiddleware
from django.utils.decorators import decorator_from_middleware_with_args

def extended_cache_page(cache_timeout, key_prefix=None, cache=None):
    return decorator_from_middleware_with_args(ExtendedCacheMiddleware)(
        cache_timeout=cache_timeout,
        cache_alias=cache,
        key_prefix=key_prefix,
    )

class ExtendedCacheMiddleware(CacheMiddleware):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if callable(self.key_prefix):
            self.key_function = self.key_prefix

    def key_function(self, request, *args, **kwargs):
        return self.key_prefix

    def get_key_prefix(self, request):
        return self.key_function(
            request,
            *request.resolver_match.args,
            **request.resolver_match.kwargs
        )

    def process_request(self, request):
        self.key_prefix = self.get_key_prefix(request)
        return super().process_request(request)

    def process_response(self, request, response):
        self.key_prefix = self.get_key_prefix(request)
        return super().process_response(request, response)

然后在你的代码中:

from django.utils.lru_cache import lru_cache

@lru_cache()
def last_modified(request, blog_id):
    """return fresh key_prefix"""

@extended_cache_page(60 * 15, key_prefix=last_modified)
def view_blog(request, blog_id):
    """view blog page with comments"""

解决方案 11:

上面的大多数解决方案在我们的案例中都不起作用,因为我们使用了https。的源代码get_cache_key显示它用于request.get_absolute_uri()生成缓存键。

默认HttpRequest类将设置schemehttp。因此我们需要重写它以用于https我们的虚拟请求对象。

这是对我们来说很好用的代码:)

from django.core.cache import cache
from django.http import HttpRequest
from django.utils.cache import get_cache_key


class HttpsRequest(HttpRequest):
    @property
    def scheme(self):
        return "https"


def invalidate_cache_page(
    path,
    query_params=None,
    method="GET",
):
    request = HttpsRequest()

    # meta information can be checked from error logs
    request.META = {
        "SERVER_NAME": "www.yourwebsite.com",
        "SERVER_PORT": "443",
        "QUERY_STRING": query_params,
    }

    request.path = path
    key = get_cache_key(request, method=method)
    if cache.has_key(key):
        cache.delete(key)

现在我可以使用此实用函数使任何视图中的缓存无效:

page = reverse('url_name', kwargs={'id': obj.id})
invalidate_cache_page(path)

解决方案 12:

Duncan 的答案适用于 Django 1.9。但如果我们需要使用 GET 参数使 URL 无效,我们必须在请求中做一些更改。例如 .../?mykey=myvalue

request.META = {'SERVER_NAME':'127.0.0.1','SERVER_PORT':8000, 'REQUEST_METHOD':'GET', 'QUERY_STRING': 'mykey=myvalue'}
request.GET.__setitem__(key='mykey', value='myvalue')

解决方案 13:

我遇到过类似的情况,这是我想到的解决方案,我在早期版本的 Django 上启动它,但它目前在 2.0.3 版本上使用。

第一个问题:当您在 Django 中设置要缓存的内容时,它会设置标头,以便下游缓存(包括浏览器缓存)缓存您的页面。

要覆盖它,您需要设置中间件。我从 StackOverflow 的其他地方抄袭了这一点,但目前找不到它。在appname/middleware.py

from django.utils.cache import add_never_cache_headers


class Disable(object):

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        add_never_cache_headers(response)
        return response

然后在settings.py到 中MIDDLEWARE添加:

'appname.middleware.downstream_caching.Disable',

请记住,这种方法会完全禁用下游缓存,这可能不是您想要的。

最后,我添加了views.py

def expire_page(request, path=None, query_string=None, method='GET'):
    """
    :param request: "real" request, or at least one providing the same scheme, host, and port as what you want to expire
    :param path: The path you want to expire, if not the path on the request
    :param query_string: The query string you want to expire, as opposed to the path on the request
    :param method: the HTTP method for the page, if not GET
    :return: None
    """
    if query_string is not None:
        request.META['QUERY_STRING'] = query_string
    if path is not None:
        request.path = path
    request.method = method

    # get_raw_uri and method show, as of this writing, everything used in the cache key
    # print('req uri: {} method: {}'.format(request.get_raw_uri(), request.method))
    key = get_cache_key(request)
    if key in cache:
        cache.delete(key)

我不喜欢传递一个request对象,但在撰写本文时,它提供了请求的方案/协议、主机和端口,只要您传递路径和查询字符串,几乎您站点/应用程序的任何请求对象都可以。

解决方案 14:

Duncan 答案的另一个更新版本:必须找出正确的元字段:(在 Django 1.9.8 上测试)

def invalidate_cache(path=''):
    import socket
    from django.core.cache import cache
    from django.http import HttpRequest
    from django.utils.cache import get_cache_key

    request = HttpRequest()
    domain = 'www.yourdomain.com'
    request.META = {'SERVER_NAME': socket.gethostname(), 'SERVER_PORT':8000, "HTTP_HOST": domain, 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br'}
    request.LANGUAGE_CODE = 'en-us'
    request.path = path

    try:
        cache_key = get_cache_key(request)
        if cache_key :
            if cache.has_key(cache_key):
                cache.delete(cache_key)
                return (True, 'successfully invalidated')
            else:
                return (False, 'cache_key does not exist in cache')
        else:
            raise ValueError('failed to create cache_key')
    except (ValueError, Exception) as e:            
        return (False, e)

解决方案 15:

一个简单的辅助函数,用于清除给定 URL 的缓存(假设没有标头)。当通过信号连接到模型的 post_save 事件时,可能效果最佳。请参阅发送给 django-users 邮件列表的消息以了解背景信息。

from django.core.cache import cache
from django.http import HttpRequest
from django.utils.cache import get_cache_key

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

解决方案 16:

解决方案很简单,不需要任何额外的工作。

例子

@cache_page(60 * 10)
def our_team(request, sorting=None):
    ...

这将使用默认键将响应设置为缓存。

使视图缓存过期

from django.utils.cache import get_cache_key
from django.core.cache import cache

def our_team(request, sorting=None):
    # This will remove the cache value and set it to None
    cache.set(get_cache_key(request), None)

简单,干净,快速。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   3892  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   2717  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Freshdesk、ClickUp、nTask、Hubstaff、Plutio、Productive、Targa、Bonsai、Wrike。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在项目管理过程中面临着诸多痛点,如任务分配不...
项目管理系统   52  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Monday、TeamGantt、Filestage、Chanty、Visor、Smartsheet、Productive、Quire、Planview。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多项目经理和团队在管理复杂项目时,常...
开源项目管理工具   54  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Smartsheet、GanttPRO、Backlog、Visor、ResourceGuru、Productive、Xebrio、Hive、Quire。在当今快节奏的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在选择项目管理工具时常常面临困惑:...
项目管理系统   49  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用