Skip to main content

Django缓存框架

动态网站的基本权衡是,它们是动态的。每次用户请求页面时,Web服务器都会进行各种计算(从数据库查询到模板呈现到业务逻辑),以创建网站访问者所看到的页面。从处理开销的角度来看,这比标准的文件系统以外的文件系统服务器安排昂贵得多。

对于大多数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也适用于“下游”缓存,例如 乌贼 和基于浏览器的缓存。这些是您不直接控制的缓存类型,您可以通过这些缓存提供有关您网站的哪些部分应缓存的提示(通过HTTP标头),以及如何缓存。

参见

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

设置缓存

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

您的缓存首选项位于设置文件中的 CACHES 设置中。以下是 CACHES 的所有可用值的说明。

Memcached

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

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

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

要与Django一起使用Memcached:

  • BACKEND 设置为 django.core.cache.backends.memcached.MemcachedCachedjango.core.cache.backends.memcached.PyLibMCCache (取决于您选择的memcached绑定)

  • LOCATION 设置为 ip:port 值,其中 ip 是Memcached守护程序的IP地址,port 是运行Memcached的端口或 unix:path 值,其中 path 是Memcached Unix套接字文件的路径。

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

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守护程序,并且程序会将该组计算机视为 single 缓存,而无需在每台计算机上重复缓存值。要利用此功能,请在 LOCATION 中包括所有服务器地址,以分号或列表分隔。

在此示例中,高速缓存通过在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可以将其缓存的数据存储在数据库中。如果你有一个快速,良好的索引数据库服务器,这最好。

要将数据库表用作缓存后端:

  • BACKEND 设置为 django.core.cache.backends.db.DatabaseCache

  • LOCATION 设置为 tablename,即数据库表的名称。此名称可以是任何您想要的,只要它是一个有效的表名称,而不是在您的数据库中使用。

在此示例中,缓存表的名称为 my_cache_table:

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

创建缓存表

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

python manage.py createcachetable

这将在数据库中创建一个使用Django数据库缓存系统所需的正确格式的表。表的名称取自 LOCATION

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

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

migrate 一样,createcachetable 不会接触现有的表。它将只创建缺少的表。

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

多个数据库

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

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

class CacheRouter(object):
    """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 数据库。

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

文件系统缓存

基于文件的后端将每个缓存值序列化并存储为单独的文件。要使用此后端将 BACKEND 设置为 "django.core.cache.backends.filebased.FileBasedCache"LOCATION 到合适的目录。例如,要在 /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的能力,考虑本地内存缓存后端。这个缓存是per-process(见下面)和线程安全的。要使用它,请将 BACKEND 设置为 "django.core.cache.backends.locmem.LocMemCache"。例如:

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

高速缓存 LOCATION 用于识别各个存储器存储。如果您只有一个 locmem 缓存,可以省略 LOCATION;但是,如果您有多个本地内存缓存,则需要为其中至少一个指定一个名称,以保持它们分离。

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

虚拟缓存(用于开发)

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

如果您有一个生产站点在各种地方使用重型缓存,但是在开发/测试环境中,您不想缓存,并且不想将代码更改为特殊情况,则这是非常有用的。要激活虚拟缓存,请设置 BACKEND:

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

使用自定义缓存后端

虽然Django包括对大量缓存后端的支持,但有时您可能需要使用自定义缓存后端。要使用Django的外部缓存后端,请使用Python导入路径作为 CACHES 设置的 BACKEND,如此:

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

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

注意:没有一个真正令人信服的原因,例如不支持它们的主机,您应该坚持Django附带的缓存后端。它们经过了良好的测试,易于使用。

缓存参数

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

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

  • OPTIONS:应传递到高速缓存后端的任何选项。有效选项的列表将根据每个后端而有所不同,由第三方库支持的缓存后端将直接将其选项传递给底层缓存库。

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

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

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

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

  • KEY_PREFIX:一个字符串,将被自动包括(默认情况下预置)到Django服务器使用的所有缓存键。

    有关详细信息,请参阅 缓存文档

  • VERSION:由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
        }
    }
}

无效的参数被静默地忽略,以及已知参数的无效值。

每个站点的缓存

缓存设置完成后,使用缓存的最简单的方法是缓存整个网站。您将需要添加 'django.middleware.cache.UpdateCacheMiddleware''django.middleware.cache.FetchFromCacheMiddleware' 到您的 MIDDLEWARE 设置,如在此示例中:

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

注解

不,这不是一个错字:“更新”中间件必须在列表中的第一个,“获取”中间件必须是最后。细节有点模糊,但看下面的 Order of MIDDLEWARE 如果你想要完整的故事。

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

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

此外,UpdateCacheMiddleware 自动在每个 HttpResponse 中设置几个标题:

  • Last-Modified 头设置为当请求新的(非缓存的)版本的页面时的当前日期/时间。

  • Expires 标题设置为当前日期/时间加上定义的 CACHE_MIDDLEWARE_SECONDS

  • 设置 Cache-Control 标题以给出页面的最大时间 - 同样,从 CACHE_MIDDLEWARE_SECONDS 设置。

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

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

如果 USE_I18N 设置为 True,则生成的缓存密钥将包括活动 language 的名称 - 参见 Django如何发现语言偏好)。这允许您轻松缓存多语言网站,而无需自己创建缓存密钥。

高速缓存键还包括当 USE_L10N 被设置为 True 时的活动 语言 和当 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。如果多个网址指向同一视图,则每个网址都将单独缓存。继续 my_view 示例,如果您的URLconf看起来像这样:

urlpatterns = [
    url(r'^foo/([0-9]{1,2})/$', my_view),
]

那么对 /foo/1//foo/23/ 的请求将单独缓存,正如您所期望的。但是一旦已经请求了特定URL(例如,/foo/23/),对该URL的后续请求将使用高速缓存。

cache_page 还可以接受一个可选的关键字参数 cache,它指导装饰器在缓存视图结果时使用特定的缓存(从您的 CACHES 设置)。默认情况下,将使用 default 缓存,但您可以指定所需的任何缓存:

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

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

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

key_prefixcache 参数可以一起指定。 key_prefix 参数和 CACHES 下指定的 KEY_PREFIX 将被连接。

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

上一节中的示例硬编码了视图被缓存的事实,因为 cache_page 改变了 my_view 函数。这种方法将您的视图耦合到缓存系统,这是不理想的几个原因。例如,您可能希望在另一个无缓存的网站上重复使用视图函数,或者您可能希望将视图分发给可能希望在未缓存的情况下使用它们的用户。这些问题的解决方案是在URLconf中指定每个视图的缓存,而不是在视图函数本身旁边。

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

urlpatterns = [
    url(r'^foo/([0-9]{1,2})/$', my_view),
]

这里是一样的,用 my_view 包裹在 cache_page:

from django.views.decorators.cache import cache_page

urlpatterns = [
    url(r'^foo/([0-9]{1,2})/$', cache_page(60 * 15)(my_view)),
]

模板片段缓存

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

{% cache %} 模板标签在给定时间量内缓存块的内容。它至少需要两个参数:缓存超时(以秒为单位)和提供缓存片段的名称。名称将被视为是,不使用变量。例如:

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

有时,您可能希望缓存片段的多个副本,具体取决于片段内部显示的某些动态数据。例如,您可能需要为上一个示例中为您网站的每个用户使用的边栏的单独的缓存副本。通过传递附加参数到 {% cache %} 模板标签来唯一标识缓存片段:

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

指定多个参数来标识片段是完全正确的。只需根据需要传递尽可能多的参数到 {% cache %}

如果 USE_I18N 设置为 True,则每个站点中间件缓存将 respect the active language。对于 cache 模板标签,您可以使用模板中提供的 translation-specific variables 之一来实现相同的结果:

{% 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以任意级别的粒度将对象存储在缓存中。您可以缓存任何可以安全pickle的Python对象:字符串,字典,模型对象列表等。 (最常见的Python对象可以pickled;有关pickling的更多信息,请参阅Python文档。)

访问缓存

django.core.cache.caches

您可以通过类似dict的对象访问在 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']

基本用法

基本接口是 set(key, value, timeout)get(key):

>>> cache.set('my_key', 'hello, world!', 30)
>>> cache.get('my_key')
'hello, world!'

key 应该是 str (或Python 2上的 unicode),value 可以是任何可拾取的Python对象。

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

如果对象不存在于缓存中,则 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'

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

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

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

如果你想得到一个键的值或设置一个值,如果键不在缓存中,有 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)
Changed in Django 1.9:

添加 get_or_set() 方法。

还有一个 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}

要更有效地设置多个值,请使用 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 参数。

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

>>> cache.delete('a')

如果要立即清除一堆密钥,delete_many() 可以获取要清除的密钥列表:

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

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

>>> cache.clear()

您还可以分别使用 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() 方法不能保证是原子的。在那些支持原子递增/递减(最明显的是memcached后端)的后端,递增和递减操作将是原子的。然而,如果后端本身不提供增量/减量操作,则它将使用两步检索/更新来实现。

如果由高速缓存后端实现,您可以使用 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 ':'.join([key_prefix, str(version), key])

如果要以不同的方式组合这些部分,或对最终密钥应用其他处理(例如,获取关键部分的哈希摘要),则可以提供自定义键函数。

KEY_FUNCTION 高速缓存设置指定与上述 make_key() 的原型匹配的函数的虚线路径。如果提供,将使用此自定义键功能,而不是默认键组合功能。

缓存关键警告

Memcached是最常用的生产缓存后端,它不允许超过250个字符或包含空格或控制字符的缓存键,并且使用这样的键将导致异常。为了鼓励缓存可移植代码并尽量减少令人不快的惊喜,如果使用会导致memcached错误的密钥,其他内置缓存后端会发出警告(django.core.cache.backends.base.CacheKeyWarning)。

如果您使用的生产后端可以接受范围更广的密钥(自定义后端或其中一个非memcached内置后端),并希望使用此更宽的范围而不出现警告,则可以使用此代码使 CacheKeyWarning 静音在您的 INSTALLED_APPS 之一的 management 模块中:

import warnings

from django.core.cache import CacheKeyWarning

warnings.simplefilter("ignore", CacheKeyWarning)

如果您要为其中一个内置后端提供自定义键验证逻辑,则可以将其子类化,仅覆盖 validate_key 方法,并按照 using a custom cache backend 的说明进行操作。例如,要为 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."""
        ...

...并在您的 CACHES 设置的 BACKEND 部分中使用此类的虚线Python路径。

下游缓存

到目前为止,本文档专注于缓存您的 own 数据。但是另一种类型的缓存也与Web开发相关:由“下游”缓存执行的缓存。这些是在请求到达您的网站之前就为用户缓存页面的系统。

下面是一些下游缓存的例子:

  • 您的ISP可能会缓存某些页面,因此如果您请求来自 https://example.com/ 的页面,您的ISP将向您发送该页面,而无需直接访问example.com。 example.com的维护者不知道这个缓存;该ISP位于example.com和您的Web浏览器之间,透明地处理所有的缓存。

  • 您的Django网站可能位于 代理缓存 后面,例如Squid Web代理缓存(http://www.squid-cache.org/),缓存页面的性能。在这种情况下,每个请求将首先由代理处理,并且只有在需要时才会传递给您的应用程序。

  • 您的Web浏览器也缓存页面。如果网页发出相应的标头,您的浏览器将使用本地缓存副本对该页面的后续请求,甚至不再与Web页面联系,以查看它是否已更改。

下游缓存是一个很好的效率提升,但是有一个危险:许多网页的内容不同基于身份验证和许多其他变量,缓存系统盲目保存纯粹基于URL的网页可能会暴露不正确或敏感数据到后续这些页面的访问者。

例如,假设您操作Web电子邮件系统,并且“收件箱”页面的内容显然取决于哪个用户登录。如果ISP盲目缓存您的网站,则通过该ISP登录的第一个用户将拥有他们的用户特定的收件箱页面为后续访问者缓存到网站。这不酷。

幸运的是,HTTP提供了这个问题的解决方案。存在多个HTTP头部以指示下游高速缓存根据指定的变量来使其高速缓存内容不同,并且告诉高速缓存机制不缓存特定页面。我们将在接下来的部分中看一些这些标题。

使用 Vary

Vary 头部定义了高速缓存机制在构建其高速缓存密钥时应该考虑哪些请求头部。例如,如果网页的内容取决于用户的语言偏好,则该页被称为“根据语言而变化”。

默认情况下,Django的缓存系统使用请求的完全限定网址(例如 "https://www.example.com/stories/2005/?order_by=author")创建其缓存键。这意味着对该网址的每个请求都将使用相同的缓存版本,而不考虑用户代理的差异(例如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' 的东西)的优点是装饰器 addsVary 头部(可能已经存在),而不是从头开始设置它并且可能覆盖已经存在的任何东西那里。

您可以将多个标头传递给 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头的更多信息,请参阅 official Vary spec

控制缓存:使用其他头

缓存的其他问题是数据的隐私性和数据应该存储在级联的缓存中的位置的问题。

用户通常面对两种缓存:它们自己的浏览器缓存(私有缓存)和它们的提供者的缓存(公共缓存)。公共缓存由多个用户使用并由其他人控制。这会导致敏感数据出现问题 - 您不希望将您的银行帐号存储在公共缓存中。因此,Web应用程序需要一种方法来告诉缓存哪些数据是私有的,哪些是公共的。

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

from django.views.decorators.cache import cache_control

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

这个装饰器负责在后台发出适当的HTTP标头。

注意,高速缓存控制设置“private”和“public”是互斥的。装饰器确保“public”指令被删除,如果“private”应该被设置(反之亦然)。这两个指令的示例使用将是提供私人和公共条目的博客站点。公共条目可以缓存在任何共享缓存上。下面的代码使用 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 使用缓存中间件,它已经使用 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 registry 中找到(请注意,并非所有伪指令都适用于响应)。

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

from django.views.decorators.cache import never_cache

@never_cache
def myview(request):
    ...

MIDDLEWARE 的顺序

如果你使用缓存中间件,重要的是把每一半放在 MIDDLEWARE 设置中的正确位置。这是因为缓存中间件需要知道通过哪个头来改变缓存存储。中间件总是在 Vary 响应头部添加一些东西。

UpdateCacheMiddleware 在响应阶段运行,其中中间件以相反的顺序运行,因此列表顶部的项在响应阶段运行 last。因此,您需要确保 UpdateCacheMiddleware 出现 before 任何其他中间件,可能会添加到 Vary 头。以下中间件模块:

  • SessionMiddleware 添加 Cookie

  • GZipMiddleware 添加 Accept-Encoding

  • LocaleMiddleware 添加 Accept-Language

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