Срок действия кеша просмотра в Django? - PullRequest
42 голосов
/ 15 февраля 2010

@cache_page decorator потрясающий. Но для моего блога я хотел бы сохранить страницу в кеше, пока кто-то не прокомментирует сообщение. Это звучит как отличная идея, так как люди редко комментируют, поэтому хранить страницы в memcached, пока никто не комментирует, было бы замечательно. Я думаю, что кто-то должен был иметь эту проблему раньше? И это отличается от кэширования на URL.

Итак, решение, о котором я думаю, это:

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

И тогда я бы сохранил список всех ключей кеша, используемых для просмотра блога, и затем мог бы исчерпать пространство кеша блога. Но я не очень опытен с Django, поэтому мне интересно, знает ли кто-нибудь лучший способ сделать это?

Ответы [ 15 ]

44 голосов
/ 02 марта 2010

Это решение работает для версий django до 1.7

Вот решение, которое я написал, чтобы сделать то, о чем вы говорите на некоторых из моих собственных проектов:

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 и срок действия кэшированного представления истекает. NB: я не проверял это всесторонне, но пока он работал нормально для меня.

11 голосов
/ 05 мая 2011

Я написал 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 (): он немного сложнее, но он позволит вам автоматически кэшировать ответы на основе изменений сущности модели.

9 голосов
/ 20 марта 2018

С последней версией 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)

Вы можете зарегистрировать invalidate_cache в качестве обратного вызова, когда кто-то обновляет сообщение в блоге с помощью сигнала pre_save.

6 голосов
/ 15 февраля 2010

В конце концов, декоратор cache_page будет использовать CacheMiddleware, который сгенерирует ключ кеша на основе запроса (см. django.utils.cache.get_cache_key) и key_prefix (в вашем случае - «blog»). Обратите внимание, что «блог» - это всего лишь префикс, а не весь ключ кэша.

Когда вы сохраняете комментарий, вы можете получить уведомление с помощью django post_save , затем вы можете попытаться создать ключ кэша для соответствующих страниц и, наконец, сказать cache.delete(key).

Однако для этого требуется ключ cache_key, который создается с запросом для ранее кэшированного представления. Этот объект запроса недоступен при сохранении комментария. Вы можете создать ключ кэша без надлежащего объекта запроса, но эта конструкция происходит в функции, помеченной как private (_generate_cache_header_key), поэтому вы не должны использовать эту функцию напрямую. Тем не менее, вы можете создать объект с атрибутом пути, который совпадает с исходным кэшированным представлением, и Django этого не заметит, но я не рекомендую этого.

Декоратор cache_page довольно медленно абстрагирует вас от кэширования и затрудняет непосредственное удаление определенного объекта кэша. Вы можете создавать свои собственные ключи и обрабатывать их таким же образом, но это требует некоторого дополнительного программирования и не так абстрактно, как у декоратора cache_page.

Вам также придется удалить несколько объектов кэша, когда ваши комментарии отображаются в нескольких представлениях (т. Е. Индексная страница с количеством комментариев и страницами отдельных записей в блоге).

Подводя итог: Django делает для вас время истечения срока действия ключей кеша, но пользовательское удаление ключей кеша в нужное время более сложно.

5 голосов
/ 25 апреля 2016

Аннулирование кэша представления 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/')

5 голосов
/ 16 октября 2014

Это не сработает на 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, получить правильный ключ и выполнить очистку для каждого.

3 голосов
/ 30 апреля 2017

У меня была такая же проблема, и я не хотел связываться с 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

3 голосов
/ 17 сентября 2011

FWIW Мне пришлось изменить решение 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: /2021768/srok-deistviya-kesha-prosmotra-v-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
3 голосов
/ 16 февраля 2010

Вместо использования кеш-декоратора страниц вы можете вручную кешировать объект публикации блога (или аналогичный), если нет комментариев, а затем, когда есть первый комментарий, повторно кэшировать объект сообщения блога так что это актуально (при условии, что у объекта есть атрибуты, которые ссылаются на любые комментарии), но затем просто дайте сроку действия этих кэшированных данных для прокомментированной записи в блоге, а затем не беспокойте повторное кэширование ...

1 голос
/ 29 февраля 2016

Вместо явного истечения срока действия кэша вы, вероятно, могли бы использовать новый "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"""
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...