Django的缓存框架


当前第2页 返回上一页

默认情况下,缓存标签将尝试使用名为“ template_fragments”的缓存。如果不存在这样的缓存,它将退回到使用默认缓存。您可以选择与using关键字参数一起使用的备用缓存后端,该参数必须是标记的最后一个参数。

{% cache 300 local-thing ...  using="localcache" %}

指定未配置的缓存名称被视为错误。

django.core.cache.utils.make_template_fragment_keyfragment_namevary_on =无

如果要获取用于缓存片段的缓存密钥,可以使用 make_template_fragment_key。fragment_name与cachetemplate标签的第二个参数相同;vary_on是传递给标记的所有其他参数的列表。该功能对于使缓存项无效或覆盖很有用,例如:

>>> from django.core.cache import cache
>>> from django.core.cache.utils import make_template_fragment_key
# cache key for {% cache 500 sidebar username %}
>>> key = make_template_fragment_key('sidebar', [username])
>>> cache.delete(key) # invalidates cached template fragment

低级缓存API

有时,缓存整个渲染的页面并不会带来太多好处,实际上,这会带来不便。

例如,也许您的站点包含一个视图,该视图的结果取决于几个昂贵的查询,这些查询的结果以不同的间隔更改。在这种情况下,使用每个站点或每个视图缓存策略提供的全页缓存并不理想,因为您不想缓存整个结果(因为某些数据经常更改),但您仍想缓存很少更改的结果。

对于此类情况,Django会公开一个低级缓存API。您可以使用此API以所需的任意粒度级别将对象存储在缓存中。您可以缓存可以安全腌制的任何Python对象:字符串,字典,模型对象列表等。(可以对大多数常见的Python对象进行腌制;有关腌制的更多信息,请参考Python文档。)

访问缓存

django.core.cache.caches

您可以CACHES通过类似dict的对象访问在设置中配置的缓存:django.core.cache.caches。在同一线程中重复请求相同的别名将返回相同的对象。

>>> from django.core.cache import caches
>>> cache1 = caches['myalias']
>>> cache2 = caches['myalias']
>>> cache1 is cache2
True

如果指定的键不存在,InvalidCacheBackendError将引发。

为了提供线程安全,将为每个线程返回不同的缓存后端实例。

django.core.cache.cache

作为一种快捷方式,默认高速缓存可用于 django.core.cache.cache

>>> from django.core.cache import cache

此对象等效于caches['default']

基本用法

基本界面是:

cache.setkeyvaluetimeout = DEFAULT_TIMEOUTversion = None
>>> cache.set('my_key', 'hello, world!', 30)
cache.getkeydefault = Noneversion = None
>>> cache.get('my_key')
'hello, world!'

key应该是str,并且value可以是任何可挑选的Python对象。

该timeout参数是可选的,并且默认为设置中timeout相应后端的参数CACHES(如上所述)。这是该值应存储在缓存中的秒数。传递 Nonefor timeout将永远缓存该值。一个timeout的0 将不缓存值。

如果对象在缓存中不存在,则cache.get()返回None:

>>> # Wait 30 seconds for 'my_key' to expire...
>>> cache.get('my_key')
None

我们建议不要将文字值存储None在缓存中,因为您将无法区分存储的None值和返回值为的缓存未命中None。

cache.get()可以default争论。这指定了如果对象在缓存中不存在则返回哪个值:

>>> cache.get('my_key', 'has expired')
'has expired'
cache.addkeyvaluetimeout = DEFAULT_TIMEOUTversion = None

若要仅在密钥尚不存在时添加密钥,请使用add()方法。它使用与相同的参数set(),但是如果指定的键已经存在,它将不会尝试更新缓存:

>>> cache.set('add_key', 'Initial value')
>>> cache.add('add_key', 'New value')
>>> cache.get('add_key')
'Initial value'

如果您需要知道是否add()在缓存中存储了值,则可以检查返回值。True如果存储了值,它将返回, False否则返回。

cache.get_or_setkeydefaulttimeout = DEFAULT_TIMEOUTversion = None

如果要获取键的值,或者如果键不在缓存中则要设置值,则可以使用该get_or_set()方法。它使用与相同的参数,get() 但默认设置为该键的新缓存值,而不是返回:

>>> cache.get('my_new_key')  # returns None
>>> cache.get_or_set('my_new_key', 'my new value', 100)
'my new value'

您还可以将任何callable作为默认值传递:

>>> import datetime
>>> cache.get_or_set('some-timestamp-key', datetime.datetime.now)
datetime.datetime(2014, 12, 11, 0, 15, 49, 457920)
cache.get_manykeysversion = None

还有一个get_many()接口只命中一次缓存。 get_many()返回一个字典,其中包含您要求的所有键,这些键实际上已存在于缓存中(并且尚未过期):

>>> cache.set('a', 1)
>>> cache.set('b', 2)
>>> cache.set('c', 3)
>>> cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}
cache.set_manydict超时

要更有效地设置多个值,请使用set_many()传递键值对字典:

>>> cache.set_many({'a': 1, 'b': 2, 'c': 3})
>>> cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}

像一样cache.set(),set_many()带有一个可选timeout参数。

在支持的后端(memcached)上,set_many()返回未能插入的密钥列表。

cache.deletekeyversion = None

您可以显式删除键,delete()以清除特定对象的缓存:

>>> cache.delete('a')
cache.delete_manykeysversion = None

如果要一次清除一堆键,delete_many()可以列出要清除的键列表:

>>> cache.delete_many(['a', 'b', 'c'])
cache.clear()

最后,如果要删除缓存中的所有键,请使用 cache.clear()。注意这一点。clear()将从 缓存中删除所有内容,而不仅仅是您的应用程序设置的键。

>>> cache.clear()
cache.touchkeytimeout = DEFAULT_TIMEOUTversion = None

cache.touch()为密钥设置新的到期时间。例如,要将密钥更新为从现在起10秒钟过期:

>>> cache.touch('a', 10)
True

与其他方法一样,该timeout参数是可选的,并且默认为设置中TIMEOUT相应后端的 选项CACHES。

touch()True如果按键被成功触摸,False 则返回;否则返回。

cache.incrkeydelta = 1version = None
cache.decrkeydelta = 1version = None

您也可以分别使用incr()或decr()方法递增或递减已存在的键 。默认情况下,现有的高速缓存值将递增或递减1。可以通过为递增/递减调用提供参数来指定其他递增/递减值。如果您尝试增加或减少不存在的缓存键,将引发ValueError:

>>> cache.set('num', 1)
>>> cache.incr('num')
2
>>> cache.incr('num', 10)
12
>>> cache.decr('num')
11
>>> cache.decr('num', 5)
6

注意:incr()/ decr()方法不保证是原子的。在那些支持原子增量/减量的后端(最值得注意的是,内存缓存的后端)上,增量和减量操作将是原子的。但是,如果后端本身不提供增量/减量操作,则将使用两步检索/更新来实现。

cache.close()

close()如果由缓存后端实现,则可以关闭与缓存的连接。

>>> cache.close()

注意:对于不实现close方法的缓存,它是无操作的。

缓存键前缀

如果要在服务器之间或生产环境与开发环境之间共享缓存实例,则一台服务器缓存的数据可能会被另一台服务器使用。如果服务器之间的缓存数据格式不同,则可能导致某些很难诊断的问题。

为防止这种情况,Django提供了为服务器使用的所有缓存键添加前缀的功能。保存或检索特定的缓存键后,Django将自动为缓存键添加KEY_PREFIX缓存设置的值 。

通过确保每个Django实例都有一个不同的 KEY_PREFIX,您可以确保缓存值不会发生冲突。

缓存版本

更改使用缓存值的运行代码时,可能需要清除所有现有的缓存值。最简单的方法是刷新整个缓存,但这会导致仍然有效且有用的缓存值丢失。

Django提供了一种更好的方法来定位各个缓存值。Django的缓存框架具有系统范围的版本标识符,该标识符使用VERSION缓存设置指定。此设置的值会自动与缓存前缀和用户提供的缓存键组合在一起,以获取最终的缓存键。

默认情况下,任何密钥请求都将自动包含站点默认的缓存密钥版本。但是,原始缓存功能都包含一个version参数,因此您可以指定要设置或获取的特定缓存键版本。例如:

>>> # Set version 2 of a cache key
>>> cache.set('my_key', 'hello world!', version=2)
>>> # Get the default version (assuming version=1)
>>> cache.get('my_key')
None
>>> # Get version 2 of the same key
>>> cache.get('my_key', version=2)
'hello world!'

可以使用incr_version()和decr_version()方法对特定键的版本进行递增和递减。这样可以使特定的键更改为新版本,而其他键不受影响。继续前面的示例:

>>> # Increment the version of 'my_key'
>>> cache.incr_version('my_key')
>>> # The default version still isn't available
>>> cache.get('my_key')
None
# Version 2 isn't available, either
>>> cache.get('my_key', version=2)
None
>>> # But version 3 *is* available
>>> cache.get('my_key', version=3)
'hello world!'

缓存键转换

如前两节所述,用户不能完全原样使用用户提供的缓存密钥,而是将其与缓存前缀和密钥版本结合使用以提供最终的缓存密钥。默认情况下,这三个部分使用冒号连接起来以生成最终字符串:

def make_key(key, key_prefix, version):
    return '%s:%s:%s' % (key_prefix, version, key)

如果要以不同的方式组合各部分,或对最终键进行其他处理(例如,获取键部分的哈希摘要),则可以提供自定义键功能。

的KEY_FUNCTION高速缓存设置指定匹配的原型的功能的虚线路径 make_key()的上方。如果提供,将使用此自定义按键功能代替默认的按键组合功能。

缓存键警告

Memcached是最常用的生产缓存后端,它不允许长度超过250个字符或包含空格或控制字符的缓存键,并且使用此类键会导致异常。为了鼓励缓存可移植的代码并最大程度地减少令人不快的意外,django.core.cache.backends.base.CacheKeyWarning如果使用的键会导致memcached错误,则其他内置的缓存后端会发出警告()。

如果您使用的生产后端可以接受更广泛的键范围(自定义后端或非内存缓存的内置后端之一),并且希望在没有警告的情况下使用更大范围的键,则可以CacheKeyWarning在此代码中保持静音management您之一的模块 INSTALLED_APPS:

import warnings

from django.core.cache import CacheKeyWarning

warnings.simplefilter("ignore", CacheKeyWarning)

如果您想为内置后端之一提供自定义密钥验证逻辑,则可以对其进行子类化,仅覆盖validate_key 方法,并按照说明使用自定义缓存后端。例如,要为locmem后端执行此操作,请将以下代码放在模块中:

from django.core.cache.backends.locmem import LocMemCache

class CustomLocMemCache(LocMemCache):
    def validate_key(self, key):
        """Custom validation, raising exceptions or warnings as needed."""
        ...

…并在设置的BACKEND一部分中使用指向该类的虚线Python路径 CACHES。

下游缓存

到目前为止,本文档的重点是缓存您自己的数据。但是另一种类型的缓存也与Web开发有关:由“下游”缓存执行的缓存。这些系统甚至可以在请求到达您的网站之前为用户缓存页面。

以下是下游缓存的一些示例:

  • 您的ISP可能会缓存某些页面,因此,如果您从https://example.com/请求一个页面 ,您的ISP将向您发送该页面,而无需直接访问example.com。example.com的维护者不了解这种缓存。ISP位于example.com和您的Web浏览器之间,透明地处理所有缓存。
  • 您的Django网站可能位于代理缓存(例如Squid Web代理缓存(http://www.squid-cache.org/))之后,该缓存可以缓存页面以提高性能。在这种情况下,每个请求首先将由代理处理,并且仅在需要时才将其传递给您的应用程序。
  • 您的Web浏览器也缓存页面。如果网页发出适当的标题,您的浏览器将使用本地缓存的副本来对该页面的后续请求,甚至无需再次联系该网页以查看其是否已更改。

下游缓存可以极大地提高效率,但是却存在危险:许多网页的内容基于身份验证和许多其他变量而有所不同,并且仅基于URL盲目保存页面的缓存系统可能会将不正确或敏感的数据暴露给后续这些页面的访问者。

例如,如果您使用Web电子邮件系统,则“收件箱”页面的内容取决于登录的用户。如果ISP盲目缓存了您的站点,则第一个通过该ISP登录的用户将拥有其用户。特定的收件箱页面已缓存,以供该站点的后续访问者使用。那不酷。

幸运的是,HTTP提供了解决此问题的方法。存在许多HTTP标头,以指示下游缓存根据指定的变量来区分其缓存内容,并告知缓存机制不要缓存特定页面。我们将在以下各节中介绍其中的一些标题。

使用Vary标题

的Vary报头定义哪些请求头的高速缓存机制建立其缓存键时应该考虑到。例如,如果网页的内容取决于用户的语言偏好,则称该页面“随语言而异”。

默认情况下,Django的缓存系统使用请求的标准URL(例如,)创建其缓存密钥 "https://www.example.com/stories/2005/?order_by=author"。这意味着对该URL的每个请求都将使用相同的缓存版本,而不管用户代理差异(例如Cookie或语言偏好设置)如何。但是,如果此页面根据请求标头(例如Cookie,语言或用户代理)的不同而产生不同的内容,则需要使用Vary 标头来告诉缓存机制页面输出取决于那些的东西。

要在Django中执行此操作,请使用方便的 django.views.decorators.vary.vary_on_headers()视图装饰器,如下所示:

from django.views.decorators.vary import vary_on_headers

@vary_on_headers('User-Agent')
def my_view(request):
    ...

在这种情况下,缓存机制(例如Django自己的缓存中间件)将为每个唯一的用户代理缓存页面的单独版本。

使用vary_on_headers装饰器而不是手动设置Vary标头(使用类似的东西 )的好处是,装饰器将添加到 标头(可能已经存在),而不是从头开始设置它并可能覆盖其中的任何内容。response['Vary'] = 'user-agent'Vary

您可以将多个标头传递给vary_on_headers():

@vary_on_headers('User-Agent', 'Cookie')
def my_view(request):
    ...

这告诉下游缓存在这两者上有所不同,这意味着用户代理和cookie的每种组合都将获得自己的缓存值。例如,具有用户代理Mozilla和cookie值foo=bar的请求将被认为不同于具有用户代理Mozilla和cookie值 的请求foo=ham。

因为在cookie上进行更改非常普遍,所以有一个 django.views.decorators.vary.vary_on_cookie()装饰器。这两个视图是等效的:

@vary_on_cookie
def my_view(request):
    ...

@vary_on_headers('Cookie')
def my_view(request):
    ...

您传递给的标头vary_on_headers不区分大小写; "User-Agent"和一样"user-agent"。

您也可以django.utils.cache.patch_vary_headers()直接使用辅助功能。此函数设置或添加到。例如:Vary header

from django.shortcuts import render
from django.utils.cache import patch_vary_headers

def my_view(request):
    ...
    response = render(request, 'template_name', context)
    patch_vary_headers(response, ['Cookie'])
    return response

patch_vary_headers将HttpResponse实例作为第一个参数,并将不区分大小写的标头名称的列表/元组作为第二个参数。

有关Vary标头的更多信息,请参见 官方Vary规格。

控制缓存:使用其他头文件

缓存的其他问题是数据的私密性以及应在级联缓存中存储数据的位置的问题。

用户通常面临两种缓存:他们自己的浏览器缓存(私有缓存)和提供者的缓存(公共缓存)。公共缓存由多个用户使用,并由其他人控制。这就给敏感数据带来了麻烦,例如,您不希望将银行帐号存储在公共缓存中。因此,Web应用程序需要一种方法来告诉缓存哪些数据是私有数据,哪些是公共数据。

解决方案是指示页面的缓存应为“专用”。要在Django中执行此操作,请使用cache_control()视图装饰器。例:

from django.views.decorators.cache import cache_control

@cache_control(private=True)
def my_view(request):
    ...

该装饰器负责在后台发送适当的HTTP标头。

请注意,缓存控制设置“专用”和“公用”是互斥的。装饰器确保如果应设置为“ private”,则删除“ public”指令(反之亦然)。这两个伪指令的示例用法是提供私有条目和公共条目的博客网站。公共条目可以缓存在任何共享缓存中。以下代码使用 patch_cache_control()手动方式修改缓存控制标头(由cache_control()装饰器内部调用 ):

from django.views.decorators.cache import patch_cache_control
from django.views.decorators.vary import vary_on_cookie

@vary_on_cookie
def list_blog_entries_view(request):
    if request.user.is_anonymous:
        response = render_only_public_entries()
        patch_cache_control(response, public=True)
    else:
        response = render_private_and_public_entries(request.user)
        patch_cache_control(response, private=True)

    return response

您还可以通过其他方式控制下游缓存(请参见 RFC 7234,了解有关HTTP缓存的详细信息)。例如,即使您不使用Django的服务器端缓存框架,您仍然可以使用以下命令告诉客户端将视图缓存一定的时间:最大年龄 指令:

from django.views.decorators.cache import cache_control

@cache_control(max_age=3600)
def my_view(request):
    ...

(如果你做使用缓存中间件,它已经设置max-age与值CACHE_MIDDLEWARE_SECONDS的设置。在这种情况下,自定义max_age从 cache_control()装饰将优先考虑,并且头部值将被正确地合并。)

任何有效的Cache-Control响应指令在中均有效cache_control()。这里还有更多示例:

  • no_transform=True
  • must_revalidate=True
  • stale_while_revalidate=num_seconds

可以在IANA注册中心中找到已知指令的完整列表 (请注意,并非所有指令都适用于响应)。

如果要使用标头来完全禁用缓存,请 never_cache()使用视图装饰器来添加标头,以确保浏览器或其他缓存不会缓存响应。例:

from django.views.decorators.cache import never_cache

@never_cache
def myview(request):
    ...

MIDDLEWARE

如果您使用缓存中间件,则将每一半放在MIDDLEWARE设置中的正确位置上很重要。这是因为缓存中间件需要知道通过哪些头来更改缓存存储。中间件总是尽可能在Vary响应头中添加一些内容。

UpdateCacheMiddleware在响应阶段运行,中间件以相反的顺序运行,因此列表顶部的项目在响应阶段最后运行。因此,您需要确保它UpdateCacheMiddleware 出现在任何其他可能向Vary 标头添加内容的中间件之前。以下中间件模块可以这样做:

  • SessionMiddleware 添加 Cookie
  • GZipMiddleware 添加 Accept-Encoding
  • LocaleMiddleware 添加 Accept-Language

FetchFromCacheMiddleware另一方面,在请求阶段运行,其中中间件首先应用,因此列表顶部的项目在请求阶段首先运行。该FetchFromCacheMiddleware还需要经过其他中间件更新运行Vary头,所以 FetchFromCacheMiddleware必须在后的是这样做的任何项目。

详情参考: https://docs.djangoproject.com/en/3.0/



标签:Django

返回前面的内容

相关阅读 >>

将mixins与基于类的视图一起使用

使用基于类的视图进行表单处理

Django的缓存框架

Django 处理http请求

Django 简介

Django 模型

基于类的视图

Django 的性能与优化

部署清单

部署静态文章

更多相关阅读请进入《Django》频道 >>




打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

评论

管理员已关闭评论功能...