Django 的缓存框架 — Django 文档

来自菜鸟教程
Django/docs/2.2.x/topics/cache
跳转至:导航、​搜索

Django 的缓存框架

动态网站的一个基本权衡是,它们是动态的。 每次用户请求页面时,Web 服务器都会进行各种计算——从数据库查询到模板呈现再到业务逻辑——以创建站点访问者看到的页面。 从处理开销的角度来看,这比标准的 read-a-file-off-the-filesystem 服务器安排要贵得多。

对于大多数 Web 应用程序来说,这种开销并不是什么大问题。 大多数 Web 应用程序不是 washingtonpost.comslashdot.org; 它们只是流量一般的中小型网站。 但对于中高流量站点,尽可能减少开销是必不可少的。

这就是缓存的用武之地。

缓存某些东西是为了保存昂贵计算的结果,以便您下次不必执行计算。 下面是一些伪代码,解释了动态生成的网页如何工作:

given a URL, try finding that page in the cache
if the page is in the cache:
    return the cached page
else:
    generate the page
    save the generated page in the cache (for next time)
    return the generated page

Django 带有一个强大的缓存系统,可以让您保存动态页面,因此不必为每个请求计算它们。 为方便起见,Django 提供了不同级别的缓存粒度:您可以缓存特定视图的输出,您可以只缓存难以生成的部分,或者您可以缓存整个站点。

Django 也适用于“下游”缓存,例如 Squid 和基于浏览器的缓存。 这些是您不能直接控制的缓存类型,但您可以向其提供有关应缓存站点的哪些部分以及如何缓存的提示(通过 HTTP 标头)。

也可以看看

缓存框架设计理念解释了框架的一些设计决策。


设置缓存

缓存系统需要少量设置。 也就是说,你必须告诉它你的缓存数据应该存放在哪里——无论是在数据库中、文件系统上还是直接在内存中。 这是一个影响缓存性能的重要决定; 是的,某些缓存类型比其他缓存类型更快。

您的缓存首选项位于设置文件中的 :setting:`CACHES` 设置中。 这里解释了 :setting:`CACHES` 的所有可用值。

内存缓存

Django 原生支持的最快、最高效的缓存类型,Memcached 是一个完全基于内存的缓存服务器,最初开发用于处理 LiveJournal.com 的高负载,随后由 Danga Interactive 开源。 Facebook 和维基百科等网站使用它来减少数据库访问并显着提高网站性能。

Memcached 作为守护进程运行,并被分配指定数量的 RAM。 它所做的只是提供一个快速接口,用于在缓存中添加、检索和删除数据。 所有数据都直接存储在内存中,因此没有数据库或文件系统使用的开销。

安装 Memcached 本身后,您需要安装 Memcached 绑定。 有几个可用的 Python Memcached 绑定; 两个最常见的是 python-memcachedpylibmc

在 Django 中使用 Memcached:

  • :设置:`后端 `django.core.cache.backends.memcached.MemcachedCache或者django.core.cache.backends.memcached.PyLibMCCache (取决于您选择的 memcached 绑定)
  • :设置:`位置 `ip:port值,其中ip是 Memcached 守护进程的 IP 地址和port是运行 Memcached 的端口,或unix:path值,其中path是 Memcached Unix 套接字文件的路径。

在本例中,Memcached 使用 python-memcached 绑定在 localhost (127.0.0.1) 端口 11211 上运行:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': '127.0.0.1:11211',
    }
}

在本例中,Memcached 可通过本地 Unix 套接字文件 /tmp/memcached.sock 使用 python-memcached 绑定:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': 'unix:/tmp/memcached.sock',
    }
}

使用 pylibmc 绑定时,不要包含 unix:/ 前缀:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
        'LOCATION': '/tmp/memcached.sock',
    }
}

Memcached 的一项出色功能是能够在多个服务器上共享缓存。 这意味着您可以在多台机器上运行 Memcached 守护进程,并且程序会将这组机器视为一个 单个 缓存,而无需在每台机器上复制缓存值。 要利用此功能,请将所有服务器地址包含在 :设置:`位置 ` , 作为分号或逗号分隔的字符串,或作为列表。

在此示例中,缓存由在 IP 地址 172.19.26.240 和 172.19.26.242 上运行的 Memcached 实例共享,均在端口 11211 上:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': [
            '172.19.26.240:11211',
            '172.19.26.242:11211',
        ]
    }
}

在以下示例中,缓存由在 IP 地址 172.19.26.240(端口 11211)、172.19.26.242(端口 11212)和 172.19.26.244(端口 11213)上运行的 Memcached 实例共享:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': [
            '172.19.26.240:11211',
            '172.19.26.242:11212',
            '172.19.26.244:11213',
        ]
    }
}

关于 Memcached 的最后一点是基于内存的缓存有一个缺点:因为缓存的数据存储在内存中,如果您的服务器崩溃,数据将丢失。 显然,内存并非用于永久数据存储,因此不要依赖基于内存的缓存作为唯一的数据存储。 毫无疑问,Django 缓存后端的 none 应该用于永久存储——它们都是缓存的解决方案,而不是存储——但我们在这里指出这一点,因为基于内存的缓存特别暂时的。


数据库缓存

Django 可以将其缓存数据存储在您的数据库中。 如果您有一个快速、索引良好的数据库服务器,这将最有效。

使用数据库表作为缓存后端:

  • :设置:`后端 `django.core.cache.backends.db.DatabaseCache
  • :设置:`位置 `tablename , 数据库表的名称。 该名称可以是您想要的任何名称,只要它是尚未在您的数据库中使用的有效表名即可。

在本例中,缓存表的名称是 my_cache_table

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
        'LOCATION': 'my_cache_table',
    }
}

创建缓存表

在使用数据库缓存之前,您必须使用以下命令创建缓存表:

python manage.py createcachetable

这会在您的数据库中创建一个表,该表采用 Django 的数据库缓存系统所期望的正确格式。 表名取自 :设置:`位置 ` .

如果您使用多个数据库缓存,:djadmin:`createcachetable` 为每个缓存创建一个表。

如果您使用多个数据库,:djadmin:`createcachetable` 会观察您的数据库路由器的 allow_migrate() 方法(见下文)。

:djadmin:`migrate`:djadmin:`createcachetable` 不会触及现有表。 它只会创建丢失的表。

要打印将要运行的 SQL,而不是运行它,请使用 createcachetable --dry-run 选项。


多个数据库

如果您对多个数据库使用数据库缓存,您还需要为您的数据库缓存表设置路由指令。 出于路由目的,数据库缓存表在名为 django_cache 的应用程序中显示为名为 CacheEntry 的模型。 此模型不会出现在模型缓存中,但模型详细信息可用于路由目的。

例如,以下路由器会将所有缓存读取操作定向到 cache_replica,并将所有写入操作定向到 cache_primary。 缓存表只会同步到 cache_primary

class CacheRouter:
    """A router to control all database cache operations"""

    def db_for_read(self, model, **hints):
        "All cache read operations go to the replica"
        if model._meta.app_label == 'django_cache':
            return 'cache_replica'
        return None

    def db_for_write(self, model, **hints):
        "All cache write operations go to primary"
        if model._meta.app_label == 'django_cache':
            return 'cache_primary'
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        "Only install the cache model on primary"
        if app_label == 'django_cache':
            return db == 'cache_primary'
        return None

如果不为数据库缓存模型指定路由方向,缓存后端将使用 default 数据库。

当然,如果您不使用数据库缓存后端,则无需担心为数据库缓存模型提供路由指令。


文件系统缓存

基于文件的后端将每个缓存值序列化并将其存储为单独的文件。 使用这个后端集 :设置:`后端 `"django.core.cache.backends.filebased.FileBasedCache":设置:`位置 ` 到合适的目录。 例如,要将缓存数据存储在 /var/tmp/django_cache 中,请使用以下设置:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': '/var/tmp/django_cache',
    }
}

如果您使用的是 Windows,请将驱动器号放在路径的开头,如下所示:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': 'c:/foo/bar',
    }
}

目录路径应该是绝对路径——也就是说,它应该从文件系统的根目录开始。 是否在设置末尾添加斜线并不重要。

确保此设置指向的目录存在,并且您的 Web 服务器在其下运行的系统用户可以读写。 继续上面的例子,如果您的服务器以用户apache运行,请确保目录/var/tmp/django_cache存在并且用户apache可读可写。


本地内存缓存

如果您的设置文件中未指定另一个缓存,则这是默认缓存。 如果您想要内存缓存的速度优势,但没有运行 Memcached 的能力,请考虑本地内存缓存后端。 此缓存是按进程(见下文)和线程安全的。 要使用它,请设置 :设置:`后端 `"django.core.cache.backends.locmem.LocMemCache" . 例如:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'unique-snowflake',
    }
}

缓存 :设置:`位置 ` 用于识别单个内存存储。 如果你只有一个locmem缓存,你可以省略 :设置:`位置 ` ; 但是,如果您有多个本地内存缓存,则需要为其中至少一个分配一个名称,以便将它们分开。

缓存使用最近最少使用 (LRU) 剔除策略。

请注意,每个进程都有自己的私有缓存实例,这意味着不可能进行跨进程缓存。 这显然也意味着本地内存缓存不是特别节省内存,因此它可能不是生产环境的好选择。 很适合开发。

2.1 版本变化: 旧版本使用伪随机剔除策略而不是 LRU。


虚拟缓存(用于开发)

最后,Django 带有一个“虚拟”缓存,它实际上并不缓存——它只是实现了缓存接口而不做任何事情。

如果您有一个在不同地方使用重型缓存的生产站点,但在开发/测试环境中您不想缓存并且不想必须将代码更改为特殊情况,则这很有用。 要激活虚拟缓存,请设置 :设置:`后端 ` 像这样:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
    }
}

使用自定义缓存后端

虽然 Django 包括对许多开箱即用的缓存后端的支持,但有时您可能希望使用自定义的缓存后端。 要在 Django 中使用外部缓存后端,请使用 Python 导入路径作为 :设置:`后端 `:设置:`缓存` 设置,像这样:

CACHES = {
    'default': {
        'BACKEND': 'path.to.backend',
    }
}

如果您正在构建自己的后端,则可以使用标准缓存后端作为参考实现。 您将在 Django 源代码的 django/core/cache/backends/ 目录中找到代码。

注意:如果没有真正令人信服的理由,例如不支持它们的主机,您应该坚持使用 Django 包含的缓存后端。 它们已经过充分测试并且易于使用。


缓存参数

可以为每个缓存后端提供额外的参数来控制缓存行为。 这些参数在 :setting:`CACHES` 设置中作为附加键提供。 有效参数如下:

  • :设置:`超时 ` :用于缓存的默认超时(以秒为单位)。 此参数默认为 300 秒(5 分钟)。 您可以将 TIMEOUT 设置为 None,以便默认情况下缓存键永不过期。 0 的值会导致键立即过期(有效地“不缓存”)。

  • :设置:`选项 ` :应该传递给缓存后端的任何选项。 每个后端的有效选项列表都会有所不同,第三方库支持的缓存后端会将它们的选项直接传递给底层缓存库。

    实现自己的剔除策略的缓存后端(即 locmemfilesystemdatabase 后端)将遵循以下选项:

    • MAX_ENTRIES:删除旧值之前缓存中允许的最大条目数。 此参数默认为 300

    • CULL_FREQUENCY:达到 MAX_ENTRIES 时被剔除的条目的比例。 实际比例为1 / CULL_FREQUENCY,因此将CULL_FREQUENCY设置为2以在达到MAX_ENTRIES时剔除一半的条目。 这个参数应该是一个整数,默认为 3

      CULL_FREQUENCY0 值意味着当达到 MAX_ENTRIES 时将转储整个缓存。 在某些后端(特别是 database)上,这使得剔除 ' 的速度更快,但代价是更多的缓存未命中。

    Memcached 后端传递的内容 :设置:`选项 ` 作为客户端构造函数的关键字参数,允许对客户端行为进行更高级的控制。 例如用法,见下文。

  • :设置:`KEY_PREFIX ` :将自动包含(默认情况下)到 Django 服务器使用的所有缓存键的字符串。

    有关更多信息,请参阅 缓存文档

  • :设置:`版本 ` : Django 服务器生成的缓存键的默认版本号。

    有关更多信息,请参阅 缓存文档

  • :设置:`KEY_FUNCTION ` 一个字符串,它包含一个函数的虚线路径,该函数定义了如何将前缀、版本和键组合成最终的缓存键。

    有关更多信息,请参阅 缓存文档

在此示例中,文件系统后端配置的超时时间为 60 秒,最大容量为 1000 个项目:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': '/var/tmp/django_cache',
        'TIMEOUT': 60,
        'OPTIONS': {
            'MAX_ENTRIES': 1000
        }
    }
}

这是基于 python-memcached 的后端的示例配置,对象大小限制为 2MB:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': '127.0.0.1:11211',
        'OPTIONS': {
            'server_max_value_length': 1024 * 1024 * 2,
        }
    }
}

以下是基于 pylibmc 的后端的示例配置,该后端启用二进制协议、SASL 身份验证和 ketama 行为模式:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
        'LOCATION': '127.0.0.1:11211',
        'OPTIONS': {
            'binary': True,
            'username': 'user',
            'password': 'pass',
            'behaviors': {
                'ketama': True,
            }
        }
    }
}

每个站点的缓存

设置缓存后,使用缓存的最简单方法是缓存整个站点。 您需要将 'django.middleware.cache.UpdateCacheMiddleware''django.middleware.cache.FetchFromCacheMiddleware' 添加到 :setting:`MIDDLEWARE` 设置中,如下例所示:

MIDDLEWARE = [
    'django.middleware.cache.UpdateCacheMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',
]

笔记

不,这不是打字错误:“update”中间件必须在列表中的第一个,“fetch”中间件必须在最后。 细节有点模糊,但如果你想要完整的故事,请参阅下面的 中间件的顺序


然后,将以下必需的设置添加到您的 Django 设置文件中:

FetchFromCacheMiddleware 缓存状态为 200 的 GET 和 HEAD 响应,其中请求和响应标头允许。 对具有不同查询参数的同一 URL 的请求的响应被认为是唯一的页面并单独缓存。 这个中间件期望 HEAD 请求得到与相应 GET 请求相同的响应头; 在这种情况下,它可以为 HEAD 请求返回缓存的 GET 响应。

此外,UpdateCacheMiddleware 会自动在每个 HttpResponse 中设置一些标头:

有关中间件的更多信息,请参阅 中间件

如果视图设置了自己的缓存到期时间(即 它在其 Cache-Control 标头中有一个 max-age 部分)然后页面将被缓存直到到期时间,而不是 :setting:`CACHE_MIDDLEWARE_SECONDS`。 使用 django.views.decorators.cache 中的装饰器,您可以轻松设置视图的到期时间(使用 cache_control() 装饰器)或禁用视图的缓存(使用 never_cache()装饰器)。 有关这些装饰器的更多信息,请参阅 using other headers 部分。

如果 :setting:`USE_I18N` 设置为 True,那么生成的缓存键将包含活动 language 的名称 – 另见 Django 是如何发现的语言偏好)。 这使您可以轻松缓存多语言站点,而无需自己创建缓存键。

:setting:`USE_L10N` 设置为 True 时,缓存键还包括活动的 language时的 当前时区 :setting:`USE_TZ` 设置为 True


每个视图缓存

django.views.decorators.cache.cache_page()

使用缓存框架的更细粒度的方法是缓存各个视图的输出。 django.views.decorators.cache 定义了一个 cache_page 装饰器,它将自动为您缓存视图的响应。 它易于使用:

from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def my_view(request):
    ...

cache_page 接受一个参数:缓存超时,以秒为单位。 在上面的例子中,my_view() 视图的结果将被缓存 15 分钟。 (请注意,为了可读性,我们将其写为 60 * 1560 * 15 将计算为 900 – 即 15 分钟乘以每分钟 60 秒。)

每个视图的缓存,就像每个站点的缓存一样,与 URL 无关。 如果多个 URL 指向同一个视图,每个 URL 将被单独缓存。 继续 my_view 示例,如果您的 URLconf 如下所示:

urlpatterns = [
    path('foo/<int:code>/', my_view),
]

然后对 /foo/1//foo/23/ 的请求将被分别缓存,如您所料。 但是一旦请求了特定的 URL(例如,/foo/23/),对该 URL 的后续请求将使用缓存。

cache_page 还可以采用可选的关键字参数 cache,它指示装饰器在缓存视图时使用特定的缓存(来自 :setting:`CACHES` 设置)结果。 默认情况下,将使用 default 缓存,但您可以指定任何您想要的缓存:

@cache_page(60 * 15, cache="special_cache")
def my_view(request):
    ...

您还可以在每个视图的基础上覆盖缓存前缀。 cache_page 接受一个可选的关键字参数,key_prefix,其工作方式与中间件的 :setting:`CACHE_MIDDLEWARE_KEY_PREFIX` 设置相同。 它可以像这样使用:

@cache_page(60 * 15, key_prefix="site1")
def my_view(request):
    ...

key_prefixcache 参数可以一起指定。 这key_prefix论证和 :设置:`KEY_PREFIX ` 下指明 :设置:`缓存` 将被串联。

在 URLconf 中指定每个视图的缓存

上一节中的示例硬编码了视图被缓存的事实,因为 cache_page 改变了 my_view 函数。 这种方法将您的视图与缓存系统结合起来,由于多种原因,这并不理想。 例如,您可能希望在另一个无缓存站点上重用视图函数,或者您可能希望将视图分发给可能希望使用它们而不被缓存的人。 这些问题的解决方案是在 URLconf 中指定每个视图的缓存,而不是在视图函数本身旁边。

这样做很容易:当你在 URLconf 中引用它时,只需用 cache_page 包装视图函数。 这是之前的旧 URLconf:

urlpatterns = [
    path('foo/<int:code>/', my_view),
]

这是同样的事情,my_view 包裹在 cache_page 中:

from django.views.decorators.cache import cache_page

urlpatterns = [
    path('foo/<int:code>/', cache_page(60 * 15)(my_view)),
]

模板片段缓存

如果您需要更多控制,您还可以使用 cache 模板标签缓存模板片段。 要让您的模板访问此标签,请将 {% load cache %} 放在模板顶部附近。

{% cache %} 模板标签在给定的时间内缓存块的内容。 它至少需要两个参数:缓存超时(以秒为单位)和给出缓存片段的名称。 如果超时为 None,则该片段将被永久缓存。 该名称将按原样使用,请勿使用变量。 例如:

{% load cache %}
{% cache 500 sidebar %}
    .. sidebar ..
{% endcache %}

有时您可能希望根据片段中出现的一些动态数据缓存片段的多个副本。 例如,您可能需要一个单独的缓存副本,用于为您站点的每个用户提供上一个示例中使用的侧边栏。 通过将一个或多个附加参数(可能是带过滤器或不带过滤器的变量)传递给 {% cache %} 模板标签来唯一标识缓存片段来实现这一点:

{% load cache %}
{% cache 500 sidebar request.user.username %}
    .. sidebar for logged in user ..
{% endcache %}

如果 :setting:`USE_I18N` 设置为 True,每个站点的中间件缓存将 尊重活动语言 。 对于 cache 模板标签,您可以使用模板中可用的 翻译特定变量 之一来实现相同的结果:

{% load i18n %}
{% load cache %}

{% get_current_language as LANGUAGE_CODE %}

{% cache 600 welcome LANGUAGE_CODE %}
    {% trans "Welcome to example.com" %}
{% endcache %}

缓存超时可以是模板变量,只要模板变量解析为整数值。 例如,如果模板变量 my_timeout 设置为值 600,则以下两个示例是等效的:

{% cache 600 sidebar %} ... {% endcache %}
{% cache my_timeout sidebar %} ... {% endcache %}

此功能可用于避免模板中的重复。 您可以在一个地方的变量中设置超时,然后重用该值。

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

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

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

django.core.cache.utils.make_template_fragment_key(fragment_name, vary_on=None)

如果要获取缓存片段使用的缓存键,可以使用make_template_fragment_keyfragment_namecache 模板标签的第二个参数相同; 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

您可以通过类似 dict 的对象访问 :setting:`CACHES` 设置中配置的缓存: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.set(key, value, timeout=DEFAULT_TIMEOUT, version=None)
>>> cache.set('my_key', 'hello, world!', 30)
cache.get(key, default=None, version=None)
>>> cache.get('my_key')
'hello, world!'

key 应该是一个 str,而 value 可以是任何可腌制的 Python 对象。

timeout 参数是可选的,默认为 :setting:`CACHES` 设置中相应后端的 timeout 参数(如上所述)。 它是值应该存储在缓存中的秒数。 为 timeout 传入 None 将永远缓存该值。 0timeout 不会缓存该值。

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

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

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

cache.get() 可以采用 default 参数。 这指定了如果缓存中不存在对象时要返回的值:

>>> cache.get('my_key', 'has expired')
'has expired'
cache.add(key, value, timeout=DEFAULT_TIMEOUT, version=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_set(key, default, timeout=DEFAULT_TIMEOUT, version=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'

您还可以将任何可调用对象作为 default 值传递:

>>> import datetime
>>> cache.get_or_set('some-timestamp-key', datetime.datetime.now)
datetime.datetime(2014, 12, 11, 0, 15, 49, 457920)
cache.get_many(keys, version=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_many(dict, timeout)

要更有效地设置多个值,请使用 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.delete(key, version=None)

您可以使用 delete() 显式删除键。 这是清除特定对象缓存的简单方法:

>>> cache.delete('a')
cache.delete_many(keys, version=None)

如果你想一次清除一堆键,delete_many()可以带一个要清除的键列表:

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

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

>>> cache.clear()
cache.touch(key, timeout=DEFAULT_TIMEOUT, version=None)

2.1 版中的新功能。


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

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

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

如果按键成功,touch() 返回 True,否则返回 False

cache.incr(key, delta=1, version=None)
cache.decr(key, delta=1, version=None)

您还可以分别使用 incr()decr() 方法增加或减少已经存在的密钥。 默认情况下,现有缓存值将增加或减少 1。 可以通过向 increment/decrement 调用提供参数来指定其他增量/减量值。 如果您尝试增加或减少不存在的缓存键,则会引发 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() 方法不保证是原子的。 在那些支持原子递增/递减的后端(最值得注意的是,memcached 后端)上,递增和递减操作将是原子的。 但是,如果后端本身不提供递增/递减操作,则将使用两步检索/更新来实现。


cache.close()

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

>>> cache.close()

笔记

对于不实现 close 方法的缓存,它是一个空操作。


缓存键前缀

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

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

通过确保每个 Django 实例具有不同的 :设置:`KEY_PREFIX ` ,您可以确保缓存值不会发生冲突。


缓存版本控制

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

Django 提供了一种更好的方法来定位单个缓存值。 Django 的缓存框架有一个系统范围的版本标识符,使用 :设置:`版本 ` 缓存设置。 此设置的值会自动与缓存前缀和用户提供的缓存键结合以获得最终的缓存键。

默认情况下,任何密钥请求都会自动包含站点默认缓存密钥版本。 但是,原始缓存函数都包含 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 个字符或包含空格或控制字符,使用此类键会导致异常。 为了鼓励缓存可移植代码并尽量减少令人不快的意外,如果使用了会导致 memcached 错误的键,其他内置缓存后端会发出警告 (django.core.cache.backends.base.CacheKeyWarning)。

如果您使用的生产后端可以接受更广泛的密钥(自定义后端,或非 memcached 内置后端之一),并希望在没有警告的情况下使用更广泛的范围,您可以静音 [ X214X] 在您的 之一的 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."""
        ...

...并使用该类的虚线 Python 路径 :设置:`后端 ` 你的一部分 :设置:`缓存` 环境。


下游缓存

到目前为止,本文档的重点是缓存您的 自己的 数据。 但是另一种类型的缓存也与 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%22。 这意味着对该 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')的优点是装饰器 添加到 [X173X ] 标头(可能已经存在),而不是从头开始设置它并可能覆盖已经存在的任何内容。

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

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

这告诉下游缓存在 both 上有所不同,这意味着用户代理和 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_headersHttpResponse 实例作为其第一个参数,将不区分大小写的标头名称列表/元组作为其第二个参数。

有关 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”是互斥的。 如果应该设置“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

您也可以通过其他方式控制下游缓存(有关 HTTP 缓存的详细信息,请参阅 RFC 7234)。 例如,即使您不使用 Django 的服务器端缓存框架,您仍然可以使用 max-age 指令告诉客户端将视图缓存一段时间:

from django.views.decorators.cache import cache_control

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

(如果你 do 使用缓存中间件,它已经使用 :setting:`CACHE_MIDDLEWARE_SECONDS` 设置的值设置了 max-age。 在这种情况下,来自 cache_control() 装饰器的自定义 max_age 将优先,并且标头值将被正确合并。)

任何有效的 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的顺序

如果您使用缓存中间件,重要的是将每一半放在 :setting:`MIDDLEWARE` 设置中的正确位置。 这是因为缓存中间件需要知道通过哪些标头来改变缓存存储。 中间件总是在可能的情况下向 Vary 响应标头添加一些内容。

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

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

另一方面,FetchFromCacheMiddleware 在请求阶段运行,中间件从头到尾应用,因此列表顶部的项目在请求阶段运行 firstFetchFromCacheMiddleware 也需要在其他中间件更新 Vary 标头后运行,所以 FetchFromCacheMiddleware 必须是 after 任何这样做的项目。