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标头提供有关缓存站点的哪些部分以及如何缓存的提示。

也可以看看

The Cache Framework design philosophy explains a few of the design decisions of the framework.

设置缓存

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

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

分布式缓存¶ T0>

The fastest, most efficient type of cache supported natively by Django, Memcached is an entirely memory-based cache server, originally developed to handle high loads at LiveJournal.com and subsequently open-sourced by 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使用python-memcached绑定在localhost(127.0.0.1)端口11211上运行:

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

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

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的一个优秀功能是它能够在多个服务器上共享缓存。 This means you can run Memcached daemons on multiple machines, and the program will treat the group of machines as a single cache, without the need to duplicate cache values on each machine. To take advantage of this feature, include all server addresses in LOCATION, either as a semicolon or comma delimited string, or as a list.

在此示例中,缓存是通过在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的最后一点是基于内存的缓存有一个缺点:因为缓存的数据存储在内存中,所以如果服务器崩溃,数据将会丢失。 显然,内存不是用于永久数据存储的,因此不要将基于内存的缓存作为唯一的数据存储。 Without a doubt, none of the Django caching backends should be used for permanent storage – they’re all intended to be solutions for caching, not storage – but we point this out here because memory-based caching is particularly temporary.

在Django 1.11中更改:

The LOCATION setting now supports defining multiple servers as a comma-delimited string.

数据库缓存

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

If you are using multiple database caches, createcachetable creates one table for each cache.

If you are using multiple databases, createcachetable observes the allow_migrate() method of your database routers (see below).

migratecreatecachetable不会触及现有的表。 它只会创建缺少的表。

要打印将要运行的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数据库。

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

文件系统缓存

基于文件的后端将每个缓存值序列化并存储为单独的文件。 要使用此后端设置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的能力,请考虑本地内存缓存后端。 这个缓存是每个进程(见下文)和线程安全的。 要使用它,请将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: The default timeout, in seconds, to use for the cache. 这个参数默认为300秒(5分钟)。 您可以将TIMEOUT设置为None,以便默认情况下缓存密钥永不过期。 A value of 0 causes keys to immediately expire (effectively “don’t cache”).

  • OPTIONS: Any options that should be passed to the cache backend. 有效选项的列表将随每个后端而变化,而由第三方库支持的缓存后端将直接将其选项传递到基础缓存库。

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

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

    • CULL_FREQUENCY: The fraction of entries that are culled when MAX_ENTRIES is reached. The actual ratio is 1 / CULL_FREQUENCY, so set CULL_FREQUENCY to 2 to cull half the entries when MAX_ENTRIES is reached. 这个参数应该是一个整数,默认为3

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

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

  • KEY_PREFIX:Django服务器使用的所有缓存键将被自动包含(默认预置)的字符串。

    有关更多信息,请参阅cache documentation

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

    有关更多信息,请参阅cache documentation

  • KEY_FUNCTION A string containing a dotted path to a function that defines how to compose a prefix, version and key into a final cache key.

    有关更多信息,请参阅cache documentation

在这个例子中,一个文件系统后端配置的超时时间为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 1.11中更改:

现在可以使用OPTIONS配置Memcached后端。

在旧版本中,您可以直接在OPTIONS内传递pylibmc行为设置。 而不赞成在OPTIONS内的behaviors键下设置它们。

每站点缓存

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

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

注意

不,这不是一个错字:“更新”中间件必须在列表中的第一个,“取”中间件必须是最后一个。 细节有些模糊,但如果您想要全文,请参阅MIDDLEWARE的订单

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

  • CACHE_MIDDLEWARE_ALIAS - 用于存储的高速缓存别名。
  • CACHE_MIDDLEWARE_SECONDS - 每个页面应该被缓存的秒数。
  • CACHE_MIDDLEWARE_KEY_PREFIX – If the cache is shared across multiple sites using the same Django installation, set this to the name of the site, or some other string that is unique to this Django instance, to prevent key collisions. 如果你不在乎,请使用空字符串。

FetchFromCacheMiddleware caches GET and HEAD responses with status 200, where the request and response headers allow. 对具有不同查询参数的相同URL的请求的响应被认为是独特的页面并被分开缓存。 这个中间件期望HEAD请求的响应头和对应的GET请求一样。在这种情况下,它可以为HEAD请求返回缓存的GET响应。

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

在Django 1.11中更改:

在旧版本中,还设置了Last-Modified标题。

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

If a view sets its own cache expiry time (i.e. it has a max-age section in its Cache-Control header) then the page will be cached until the expiry time, rather than CACHE_MIDDLEWARE_SECONDS. Using the decorators in django.views.decorators.cache you can easily set a view’s expiry time (using the cache_control() decorator) or disable caching for a view (using the never_cache() decorator). 有关这些装饰器的更多信息,请参阅使用其他标题部分。

If USE_I18N is set to True then the generated cache key will include the name of the active language – see also How Django discovers language preference). 这使您可以轻松地缓存多语言网站,而无需自己创建缓存密钥。

Cache keys also include the active language when USE_L10N is set to True and the current time zone when USE_TZ is set to True.

每个视图缓存

django.views.decorators.cache。 cache_page T0>()¶ T1>

使用缓存框架的更细化的方式是缓存单个视图的输出。 django.views.decorators.cache定义了一个cache_page装饰器,它将自动缓存视图的响应。 它很容易使用:

from django.views.decorators.cache import cache_page

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

cache_page takes a single argument: the cache timeout, in seconds. 在上面的例子中,my_view()视图的结果将被缓存15分钟。 (请注意,为了便于阅读,我们将其写成60 * 15 60 * 15 will be evaluated to 900 – that is, 15 minutes multiplied by 60 seconds per minute.)

按照每个视图的缓存,就像每个站点的缓存一样,从URL中取消。 如果多个URL指向相同的视图,则每个URL将分开缓存。 继续my_view的例子,如果你的URLconf如下所示:

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

那么正如你所期望的那样,对/foo/1//foo/23/的请求将被分别缓存。 但是一旦请求了一个特定的URL(例如/foo/23/),对该URL的后续请求将使用缓存。

cache_page can also take an optional keyword argument, cache, which directs the decorator to use a specific cache (from your CACHES setting) when caching view results. 默认情况下,将使用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参数可以一起指定。 CACHES下指定的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模板标记来缓存模板片段。 To give your template access to this tag, put {% load cache %} near the top of your template.

模板标签缓存块的内容一段给定的时间。{% cache %} 它至少需要两个参数:以秒为单位的高速缓存超时以及给出高速缓存片段的名称。 如果超时None,片段将被永久缓存。 该名称将被视为,不要使用一个变量。 例如:

{% load cache %}
{% cache 500 sidebar %}
    .. sidebar ..
{% endcache %}
在Django 2.0中更改:

旧版本不允许None超时。

有时您可能需要缓存片段的多个副本,具体取决于片段中出现的一些动态数据。 例如,您可能需要为上一个示例中的每个用户使用单独的缓存副本。 通过向{% 缓存 %}传递一个或多个附加参数(可以是带有或不带过滤器的变量) 模板标签来唯一标识缓存片段:

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

If USE_I18N is set to True the per-site middleware cache will 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_keyfragment_namevary_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

低级缓存API

有时候,缓存整个渲染的页面并不会给你带来太多的帮助,而且实际上也是一个不必要的矫枉过正。

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

对于这样的情况,Django公开了一个简单的,低级别的缓存API。 您可以使用此API将对象以任何级别的粒度存储在缓存中。 您可以缓存任何可以安全腌制的Python对象:字符串,字典,模型对象列表等等。 (最常见的Python对象可以被腌制,关于酸洗的更多信息请参考Python文档。)

访问缓存

django.core.cache。高速缓存 T0> ¶ T1>

您可以通过字典对象访问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。缓存 T0> ¶ T1>

作为一个快捷方式,默认缓存可以用作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,而value可以是任何可供选择的Python对象。

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

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

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

我们建议不要在缓存中存储文本值None,因为您无法区分存储的None值和由返回值None

cache.get() can take a default argument. 如果对象不存在于缓存中,则指定要返回哪个值:

>>> 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'

您还可以将任何可调用的值作为默认值传递:

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

还有一个只接触缓存一次的get_many()接口。 get_many() returns a dictionary with all the keys you asked for that actually exist in the cache (and haven’t expired):

>>> 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参数。

On supported backends (memcached), set_many() returns a list of keys that failed to be inserted.

在Django 2.0中更改:

添加了包含失败密钥列表的返回值。

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

>>> cache.delete('a')

If you want to clear a bunch of keys at once, delete_many() can take a list of keys to be cleared:

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

最后,如果要删除缓存中的所有密钥,请使用cache.clear() Be careful with this; clear() will remove everything from the cache, not just the keys set by your application.

>>> 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个字符的缓存键或包含空格或控制字符的缓存键,而使用这种键将导致异常。 To encourage cache-portable code and minimize unpleasant surprises, the other built-in cache backends issue a warning (django.core.cache.backends.base.CacheKeyWarning) if a key is used that would cause an error on memcached.

If you are using a production backend that can accept a wider range of keys (a custom backend, or one of the non-memcached built-in backends), and want to use this wider range without warnings, you can silence CacheKeyWarning with this code in the management module of one of your INSTALLED_APPS:

import warnings

from django.core.cache import CacheKeyWarning

warnings.simplefilter("ignore", CacheKeyWarning)

如果您想为其中一个内置后端提供自定义密钥验证逻辑,则可以对其进行子类化,仅覆盖validate_key方法,并使用自定义缓存后端遵循的指示信息 T2>。 例如,要为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路径指向此类。

下游缓存

到目前为止,这个文档已经集中于缓存你的自己的数据。 但另一种类型的缓存也与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自己的缓存中间件)将为每个唯一的用户代理缓存单独版本的页面。

The advantage to using the vary_on_headers decorator rather than manually setting the Vary header (using something like response['Vary'] = 'user-agent') is that the decorator adds to the Vary header (which may already exist), rather than setting it from scratch and potentially overriding anything that was already in there.

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

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

这就告诉下游缓存在上都有所不同,这意味着每个用户代理和cookie的组合都将获得自己的缓存值。 For example, a request with the user-agent Mozilla and the cookie value foo=bar will be considered different from a request with the user-agent Mozilla and the cookie value 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"是一样的。

You can also use a helper function, django.utils.cache.patch_vary_headers(), directly. 此函数设置或添加Vary 标题 例如:

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标题的更多信息,请参阅 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”指令被删除(反之亦然)。 这两个指令的一个例子是一个提供私人和公共条目的博客网站。 公共条目可以缓存在任何共享缓存上。 以下代码使用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 =真
  • must_revalidate =真
  • stale_while_revalidate = NUM​​_SECONDS

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

If you want to use headers to disable caching altogether, never_cache() is a view decorator that adds headers to ensure the response won’t be cached by browsers or other caches. 例:

from django.views.decorators.cache import never_cache

@never_cache
def myview(request):
    ...

MIDDLEWARE

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

UpdateCacheMiddleware runs during the response phase, where middleware is run in reverse order, so an item at the top of the list runs last during the response phase. 因此,您需要确保UpdateCacheMiddleware出现在之前的任何其他可能会添加内容到Vary标头的中间件。 以下中间件模块这样做:

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

FetchFromCacheMiddleware, on the other hand, runs during the request phase, where middleware is applied first-to-last, so an item at the top of the list runs first during the request phase. The FetchFromCacheMiddleware also needs to run after other middleware updates the Vary header, so FetchFromCacheMiddleware must be after any item that does so.