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还可以很好地与“下游”缓存配合使用,例如 Squid 和基于浏览器的缓存。这些是您不能直接控制的缓存类型,但您可以(通过HTTP头)提供有关站点的哪些部分应该缓存以及如何缓存的提示。

参见

这个 Cache Framework design philosophy 解释框架的一些设计决策。

设置缓存

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

您的缓存首选项进入 CACHES 设置文件中的设置。以下是所有可用值的解释 CACHES .

内存缓存

Memcached__ 是一个完全基于内存的缓存服务器,最初是为了处理LiveJournal.com上的高负载而开发的,后来由Danga Interactive开源。Facebook和维基百科等网站使用它来减少数据库访问并显著提高网站性能。

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

在安装Memcached本身之后,您将需要安装一个Memcached绑定。有几个可用的Python Memcached绑定;Django支持的两个绑定是 pylibmcpymemcache

要将memcached与django一起使用,请执行以下操作:

  • 设置 BACKENDdjango.core.cache.backends.memcached.PyMemcacheCachedjango.core.cache.backends.memcached.PyLibMCCache (取决于您选择的memcached绑定)

  • 集合 LOCATIONip:port 价值何处 ip 是memcached守护进程的IP地址,并且 port 是运行memcached的端口,还是到 unix:path 价值何处 path 是memcached unix套接字文件的路径。

在本例中,memcached在本地主机(127.0.0.1)端口11211上运行,使用 pymemcache 装订::

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

在此示例中,memcached可通过本地Unix套接字文件使用 /tmp/memcached.sock 使用 pymemcache 装订::

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

memcached的一个优秀特性是它能够在多个服务器上共享缓存。这意味着您可以在多台机器上运行memcached守护进程,程序将把这组机器视为 单一的 缓存,无需在每台计算机上复制缓存值。要利用此功能,请将所有服务器地址包括在 LOCATION ,可以是分号或逗号分隔的字符串,也可以是列表。

在本例中,缓存在IP地址172.19.26.240和172.19.26.242上运行的memcached实例上共享,这两个实例都在端口11211上:

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
        "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.PyMemcacheCache",
        "LOCATION": [
            "172.19.26.240:11211",
            "172.19.26.242:11212",
            "172.19.26.244:11213",
        ],
    }
}

默认情况下, PyMemcacheCache 后端设置以下选项(您可以在您的 OPTIONS ):

"OPTIONS": {
    "allow_unicode_keys": True,
    "default_noreply": False,
    "serde": pymemcache.serde.pickle_serde,
}

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

雷迪斯人

Redis__ 是可用于缓存的内存中数据库。首先,您需要一台在本地或远程机器上运行的Redis服务器。

在设置Redis服务器之后,您将需要安装用于Redis的Python绑定。 redis-py Django本身是否支持绑定。安装 hiredis-py 套餐也是推荐的。

使用Redis作为Django的缓存后端:

例如,如果Redis运行在本地主机(127.0.0.1)端口6379::

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
    }
}

通常,Redis服务器受身份验证保护。为了提供用户名和密码,请将它们添加到 LOCATION 以及URL::

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": "redis://username:password@127.0.0.1:6379",
    }
}

如果在复制模式下设置了多个Redis服务器,则可以将服务器指定为分号或逗号分隔的字符串,也可以指定为列表。在使用多台服务器时,写入操作在第一台服务器(Leader)上执行。读取操作在随机选择的其他服务器(复制副本)上执行:

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": [
            "redis://127.0.0.1:6379",  # leader
            "redis://127.0.0.1:6378",  # read-replica 1
            "redis://127.0.0.1:6377",  # read-replica 2
        ],
    }
}

数据库缓存

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

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

  • 集合 BACKENDdjango.core.cache.backends.db.DatabaseCache

  • 集合 LOCATIONtablename ,数据库表的名称。这个名称可以是您想要的任何名称,只要它是数据库中尚未使用的有效表名。

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

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

与其他缓存后端不同,数据库缓存不支持在数据库级别自动剔除过期条目。相反,每次都会剔除过期的缓存条目 add()set() ,或 touch() 被称为。

创建缓存表

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

python manage.py createcachetable

这将在数据库中创建一个符合Django数据库缓存系统要求的格式的表。表名取自 LOCATION .

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

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

喜欢 migratecreatecachetable 不会碰到现有的表格。它只会创建丢失的表。

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

Multiple databases

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

例如,以下路由器将所有缓存读取操作定向到 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 数据库。

如果不使用数据库缓存后端,就不必担心为数据库缓存模型提供路由指示。

文件系统缓存

基于文件的后端将每个缓存值序列化并存储为单独的文件。要使用此后端集 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 ,或者它可以由用户创建 apache

警告

当高速缓存 LOCATION 包含在 MEDIA_ROOTSTATIC_ROOT ,或 STATICFILES_FINDERS ,敏感数据可能会暴露。

获得缓存文件访问权限的攻击者不仅可以伪造您的站点信任的HTML内容,还可以远程执行任意代码,因为数据是使用 pickle

警告

存储大量文件时,文件系统缓存可能会变慢。如果遇到此问题,请考虑使用不同的缓存机制。您还可以子类 FileBasedCache 完善淘汰策略。

本地内存缓存

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

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

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

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

请注意,每个进程都有自己的私有缓存实例,这意味着不可能进行跨进程缓存。这也意味着本地内存缓存的内存效率不高,因此对于生产环境来说,它可能不是一个好的选择。有利于发展。

虚拟缓存(用于开发)

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

如果您的生产站点在不同的地方使用重载缓存,但开发/测试环境不希望缓存,也不希望将代码更改为特殊情况(后者),那么这非常有用。要激活虚拟缓存,请设置 BACKEND 像这样::

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

使用自定义缓存后端

虽然Django包括对许多现成缓存后端的支持,但有时您可能希望使用自定义的缓存后端。要将外部缓存后端与django一起使用,请使用python导入路径作为 BACKENDCACHES 设置,就像这样:

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

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

注意:如果没有真正有说服力的原因,比如主机不支持它们,那么您应该坚持使用Django附带的缓存后端。它们已经过很好的测试,并且有很好的文档记录。

缓存参数

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

  • TIMEOUT :用于缓存的默认超时(秒)。此参数默认为 300 秒(5分钟)。你可以设定 TIMEOUTNone 这样,默认情况下,缓存键就不会过期。一个值 0 使密钥立即过期(实际上是“不缓存”)。

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

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

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

    • CULL_FREQUENCY :当 MAX_ENTRIES 达到。实际比率为 1 / CULL_FREQUENCY 如此设定 CULL_FREQUENCY2MAX_ENTRIES 达到。此参数应为整数,默认为 3 .

      一个值 0 对于 CULL_FREQUENCY 意味着当 MAX_ENTRIES 达到。在一些后端 (database 尤其是)这使得剔除 much 以牺牲更多缓存未命中为代价加快速度。

    Memcached和Redis后端传递 OPTIONS 作为客户端构造函数的关键字参数,允许对客户端行为进行更高级的控制。例如用法,见下文。

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

    cache documentation 更多信息。

  • VERSION :Django服务器生成的缓存密钥的默认版本号。

    cache documentation 更多信息。

  • KEY_FUNCTION 包含指向函数的点式路径的字符串,该函数定义如何将前缀、版本和键组合到最终缓存键中。

    cache documentation 更多信息。

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

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

下面是一个示例配置 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,
            },
        },
    }
}

下面是一个示例配置 pymemcache 基于后端,它支持客户端池(通过保持客户端连接来提高性能),将memcache/网络错误视为缓存未命中,并设置 TCP_NODELAY 连接套接字上的标志:

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
        "LOCATION": "127.0.0.1:11211",
        "OPTIONS": {
            "no_delay": True,
            "ignore_exc": True,
            "max_pool_size": 4,
            "use_pooling": True,
        },
    }
}

Here's an example configuration for a redis based backend that selects database 10 (by default Redis ships with 16 logical databases), specifies a parser class (redis.connection.HiredisParser will be used by default if the hiredis-py package is installed), and sets a custom connection pool class (redis.ConnectionPool is used by default):

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
        "OPTIONS": {
            "db": "10",
            "parser_class": "redis.connection.PythonParser",
            "pool_class": "redis.BlockingConnectionPool",
        },
    }
}

每个站点缓存

一旦设置了缓存,使用缓存的最简单方法就是缓存整个站点。您需要添加 '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设置文件中:

  • CACHE_MIDDLEWARE_ALIAS --用于存储的缓存别名。

  • CACHE_MIDDLEWARE_SECONDS --每个页面应缓存的秒数。

  • CACHE_MIDDLEWARE_KEY_PREFIX --如果使用相同的django安装在多个站点之间共享缓存,请将其设置为站点名称或此django实例独有的其他字符串,以防止键冲突。如果你不在乎的话,使用一个空字符串。

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

此外, UpdateCacheMiddleware 自动在每个邮件头中设置几个邮件头 HttpResponse 哪种影响 downstream caches

中间件 有关中间件的更多信息。

如果视图设置了自己的缓存到期时间(即它有 max-age 其截面 Cache-Control 则该页将被缓存到到期时间,而不是 CACHE_MIDDLEWARE_SECONDS . 在 django.views.decorators.cache 您可以轻松设置视图的到期时间(使用 cache_control() 或者禁用视图缓存(使用 never_cache() 装饰师)见 `using other headers`_ _有关这些装饰的更多信息。

如果 USE_I18N 设置为 True 然后生成的缓存键将包括活动的 language --也见 Django如何发现语言偏好 )这允许您轻松缓存多语言站点,而不必自己创建缓存密钥。

缓存键还包括 current time zone 什么时候? USE_TZ 设置为 True .

每视图缓存

django.views.decorators.cache.cache_page(timeout, *, cache=None, key_prefix=None)

使用缓存框架的一种更精细的方法是缓存单个视图的输出。 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 * 15 为了可读性。 60 * 15 将被评估为 900 --也就是说,每分钟15分钟乘以60秒。)

由设置的缓存超时 cache_page 优先于 max-age 来自 Cache-Control 标题。

每个视图缓存和每个站点缓存一样,都是由URL键控的。如果多个URL指向同一个视图,则每个URL将分别缓存。继续 my_view 例如,如果您的urlconf如下所示:

urlpatterns = [
    path("foo/<int:code>/", 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 参数和 KEY_PREFIX 指定下 CACHES 将连接。

此外, cache_page 自动设置 Cache-ControlExpires 响应中影响 downstream caches .

在urlconf中指定每个视图缓存

前一节中的示例硬编码了缓存视图的事实,因为 cache_page 改变了 my_view 功能到位。这种方法将您的视图与缓存系统结合在一起,这是不理想的,原因有几个。例如,您可能希望重用另一个站点上的视图函数,少缓存站点,或者您可能希望将视图分发给希望在不缓存的情况下使用视图的人。这些问题的解决方案是在urlconf中指定每个视图的缓存,而不是在视图函数本身的旁边。

用视图包装可以做到 cache_page 当您在URLCONF中引用它时。这是前面的旧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 %}

如果 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 %}
    {% translate "Welcome to example.com" %}
{% endcache %}

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

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

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

默认情况下,cache标记将尝试使用名为“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_key . fragment_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
True

低级缓存API

有时候,缓存整个呈现的页面并不能给您带来很多好处,事实上,这是不方便的过度杀戮。

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

对于这种情况,Django公开了一个低级缓存API。您可以使用此API以您喜欢的任何粒度级别在缓存中存储对象。您可以缓存任何可以安全地pickle的Python对象:字符串、字典、模型对象列表等等。(大多数常见的Python对象都可以被pickle;有关pickle的更多信息,请参阅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.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 应该是 strvalue 可以是任何可拾取的python对象。

这个 timeout 参数是可选的,默认为 timeout 中相应后端的参数 CACHES 设置(如上所述)。它是值应该存储在缓存中的秒数。传球 None 对于 timeout 将永远缓存该值。一 timeout 属于 0 不会缓存值。

如果缓存中不存在该对象, cache.get() 退货 None

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

如果您需要确定对象是否存在于缓存中,并且您已存储了文字值 None 中,使用前哨对象作为默认对象:

>>> sentinel = object()
>>> cache.get("my_key", sentinel) is sentinel
False
>>> # Wait 30 seconds for 'my_key' to expire...
>>> cache.get("my_key", sentinel) is sentinel
True

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")
True

delete() 收益率 True 如果成功删除密钥, False 否则。

cache.delete_many(keys, version=None)

如果你想一次清理一串钥匙, delete_many() 可以获取要清除的密钥列表:

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

最后,如果希望删除缓存中的所有键,请使用 cache.clear() 。小心这一点; clear() 将删除 everything 来自缓存,而不仅仅是您的应用程序设置的密钥:

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

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

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

和其他方法一样, timeout 参数是可选的,默认为 TIMEOUTCACHES 设置。

touch() 收益率 True 如果成功地触摸了钥匙, False 否则。

cache.incr(key, delta=1, version=None)
cache.decr(key, delta=1, version=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() 方法不能保证是原子的。在那些支持原子递增/递减的后端(尤其是memcached后端),递增和递减操作将是原子的。但是,如果后端本机不提供递增/递减操作,则将使用两步检索/更新来实现。

cache.close()

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

>>> cache.close()

备注

对于不实现的缓存 close 方法它是不可操作的。

备注

基方法的异步变量以前缀 a ,例如 cache.aadd()cache.adelete_many() 。看见 Asynchronous support 了解更多详细信息。

缓存密钥前缀

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

为了防止这种情况发生,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出错的键。

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

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."""
        ...

…并在 BACKEND 你的一部分 CACHES 设置。

异步支持

Django已经开发了对异步缓存后端的支持,但还不支持异步缓存。它将在未来的版本中发布。

django.core.cache.backends.base.BaseCache 具有的异步变体 all base methods 。按照约定,所有方法的异步版本都带有前缀 a 。默认情况下,两个变量的参数是相同的:

>>> await cache.aset("num", 1)
>>> await cache.ahas_key("num")
True

下游缓存

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

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

  • 使用HTTP时,您的 ISP 可能会缓存某些页面,因此如果您从 http://example.com/ ,您的互联网服务供应商会将页面发送给您,而不必直接访问Example.com。Example.com的维护人员对这种缓存一无所知;isp位于Example.com和您的Web浏览器之间,透明地处理所有的缓存。这种缓存在HTTPS下是不可能的,因为它会构成中间人攻击。

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

  • 你的网络浏览器也会缓存页面。如果网页发出适当的标头,您的浏览器将使用本地缓存的副本来处理对该网页的后续请求,甚至无需再次联系该网页以查看其是否已更改。

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

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

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

使用 Vary 报头

这个 Vary Header定义了缓存机制在构建其缓存键时应考虑哪些请求头。例如,如果网页的内容取决于用户的语言首选项,则该页面被称为“随语言而异”。

默认情况下,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.headers['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 .

因为 cookies 的变化是很常见的, 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 实例作为其第一个参数,不区分大小写的头名称列表/元组作为其第二个参数。

有关不同标头的更多信息,请参阅 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”是互斥的。如果应该设置“private”,则decorator确保删除“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 9111 有关HTTP缓存的详细信息)。例如,即使您不使用Django的服务器端缓存框架,您仍然可以使用 max-age 指令::

from django.views.decorators.cache import cache_control


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

(如果你 do 使用缓存中间件,它已经设置了 max-age 价值 CACHE_MIDDLEWARE_SECONDS 设置。在这种情况下,习惯 max_agecache_control() decorator将优先,并且头值将正确合并。)

任何有效 Cache-Control 响应指令在中有效 cache_control() . 以下是更多示例:

  • no_transform=True

  • must_revalidate=True

  • stale_while_revalidate=num_seconds

  • no_cache=True

已知指令的完整列表可以在 IANA registry (注意,并非所有这些都适用于回答)。

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

from django.views.decorators.cache import never_cache


@never_cache
def myview(request):
    ...

秩序 MIDDLEWARE

如果您使用缓存中间件,那么在 MIDDLEWARE 设置。这是因为缓存中间件需要知道哪些报头可以改变缓存存储。中间件总是在 Vary 响应头(如果可以)。

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

  • SessionMiddleware adds Cookie

  • GZipMiddleware adds Accept-Encoding

  • LocaleMiddleware adds Accept-Language

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