头部背景图

Django 的5种缓存框架Memcached、Redis、DatabaseCache等各种缓存用法,以及

:soogor 2022-09-15 14:35:30 :28
动态网站的一个基本权衡是,它们是动态的。每次用户请求页面时,Web 服务器都会进行各种计算——从数据库查询到模板呈现再到业务逻辑——以创建站点访问者看到的页面。从处理开销的角度来看,这比标准的 read-a-file-off-the-filesystem 服务器安排要昂贵得多。对于大多数 Web 应用程序来说,这种开销并不是什么大问题。大多数 Web 应用程序不是washingtonpost.co

 

动态网站的一个基本权衡是,它们是动态的。每次用户请求页面时,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 标头)关于您的站点的哪些部分应该被缓存,以及如何缓存。

也可以看看

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

设置缓存

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

您的缓存首选项位于CACHES设置文件中的设置中。这是对所有可用值的解释 CACHES

Memcached

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

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

安装 Memcached 本身后,您需要安装 Memcached 绑定。有几个 Python Memcached 绑定可用;Django 支持的两个是pylibmc和pymemcache。两个任选一个安装就可以了,pylibmc我们是没有安装 成功,我们直接安装 的pymemcache

.

pip install pymemcache

 

将 Memcached 与 Django 一起使用:

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

在此示例中,Memcached 使用以下pymemcache绑定在 localhost (127.0.0.1) 端口 11211 上运行:

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

在此示例中,Memcached 可通过 /tmp/memcached.sock使用pymemcache绑定的本地 Unix 套接字文件获得:

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',
        ]
    }
}

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

在 Django 3.2 中更改:

添加了PyMemcacheCache后端。

自 3.2 版起已弃用:不推荐使用MemcachedCache后端,因为python-memcached它存在一些问题并且似乎未维护。使用PyMemcacheCache或 PyLibMCCache代替。

Redis

Django 4.0 中的新功能。

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

设置 Redis 服务器后,您需要为 Redis 安装 Python 绑定。redis-py是 Django 原生支持的绑定。还建议安装额外的hiredis-py包。

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

例如,如果 Redis 在 localhost (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 服务器,则可以将服务器指定为分号或逗号分隔的字符串,或者作为列表。使用多台服务器时,写入操作在第一台服务器(领导者)上执行。读取操作在随机选择的其他服务器(副本)上执行:

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 可以将其缓存的数据存储在您的数据库中。如果您有一个快速、索引良好的数据库服务器,这将最有效。

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

  • 设置BACKEND为 django.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

多个数据库

如果您对多个数据库使用数据库缓存,您还需要为您的数据库缓存表设置路由指令。出于路由的目的,数据库缓存表在名为 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.

本地内存缓存

如果您的设置文件中未指定另一个缓存,则这是默认缓存。如果您想要内存缓存的速度优势,但又没有运行 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 导入路径作为 BACKEND设置CACHES,如下所示:

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

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

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

缓存参数

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

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

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

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

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

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

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

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

  • 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
        }
    }
}

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基于后端的示例配置,它启用客户端池(这可以通过保持客户端连接来提高性能),将内存缓存/网络错误视为缓存未命中,并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,
        }
    }
}

这是一个redis基于后端的示例配置,它选择数据库10(默认情况下 Redis 附带 16 个逻辑数据库),指定 解析器类redis.connection.HiredisParser如果安装了包,将默认使用hiredis-py),并设置自定义连接池类redis.ConnectionPool由默认):

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

笔记

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

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

  • CACHE_MIDDLEWARE_ALIAS– 用于存储的缓存别名。
  • CACHE_MIDDLEWARE_SECONDS– 每页应缓存的秒数。
  • CACHE_MIDDLEWARE_KEY_PREFIX– 如果使用相同的 Django 安装在多个站点之间共享缓存,请将其设置为站点的名称,或此 Django 实例唯一的其他字符串,以防止键冲突。如果您不在乎,请使用空字符串。

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

此外,UpdateCacheMiddleware自动在每个HttpResponse中设置一些影响下游缓存的标头:

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

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

如果USE_I18N设置为,True则生成的缓存键将包含活动语言的名称——另请参阅 Django 如何发现语言首选项)。这使您可以轻松地缓存多语言站点,而无需自己创建缓存键。

当设置为时,缓存键还包括当前时区USE_TZTrue

按视图缓存

django.views.decorators.cache.cache_pagetimeout , * , 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 分钟。(请注意,我们编写它是为了便于阅读。将被评估为- 即 15 分钟乘以每分钟 60 秒。)60 * 1560 * 15900

设置的缓存超时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_prefix。参数和指定的cacheunder 将被连接起来。key_prefixKEY_PREFIXCACHES

此外,在影响下游缓存cache_page的响应中自动设置Cache-Control和 标头。Expires

在 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 %}

模板标签在给定的时间内缓存块的内容。它至少需要两个参数:缓存超时(以秒为单位)和提供缓存片段的名称。如果 timeout 为 ,则片段将被永久缓存。名称将按原样使用,请勿使用变量。例如:{% 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每站点中间件缓存将 尊重活动语言。对于cache模板标签,您可以使用模板中可用的 翻译特定变量之一来获得相同的结果:

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

{% get_current_language as LANGUAGE_CODE %}

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

缓存超时可以是模板变量,只要模板变量解析为整数值。例如,如果将模板变量 my_timeout设置为 value 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_name , vary_on None ) 

如果要获取用于缓存片段的缓存键,可以使用 make_template_fragment_key. 与模板标签fragment_name的第二个参数相同;是传递给标签的所有附加参数的列表。此函数可用于使缓存项无效或覆盖,例如:cachevary_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 

有时,缓存整个呈现的页面并不会给您带来太多好处,而且实际上是不方便的矫枉过正。

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

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

访问缓存

django.core.cache.caches

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

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

如果命名键不存在,InvalidCacheBackendError将被引发。

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

django.core.cache.cache

作为快捷方式,默认缓存可用作 django.core.cache.cache

>>> from django.core.cache import cache

该对象等价于caches['default']

基本用法

基本界面如下:

cache.set超时DEFAULT_TIMEOUT版本=

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

cache.get默认=版本=

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

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

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

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

MemcachedCache

由于python-memcached限制,无法区分存储的值和不推荐使用的后端上的None返回值表示的缓存未命中。NoneMemcachedCache

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

>>> cache.get('my_key', 'has expired')
'has expired'

cache.add超时DEFAULT_TIMEOUT版本=

要仅在密钥尚不存在时添加密钥,请使用该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默认超时DEFAULT_TIMEOUT版本=

如果要获取键的值或在键不在缓存中的情况下设置值,则有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)

cache.get_many版本=

还有一个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(字典,超时

要更有效地设置多个值,请使用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版本=

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

>>> cache.delete('a')
True

delete()True如果密钥被成功删除,则返回,False 否则。

cache.delete_many版本=

如果您想一次清除一堆键,delete_many()可以获取要清除的键列表:

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

cache.clear()

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

>>> cache.clear()

cache.touch超时DEFAULT_TIMEOUT版本=

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

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

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

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

cache.incr增量1版本=

cache.decr增量1版本=

您还可以分别使用incr()or方法增加或减少已存在的键 。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()。有关更多详细信息,请参阅异步支持 。

在 Django 4.0 中更改:

方法的异步变体已添加到BaseCache.

缓存键前缀

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

为了防止这种情况,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 方法,然后按照使用自定义缓存后端的说明进行操作。例如,要为locmem后端执行此操作,请将以下代码放入模块中:

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

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

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

异步支持

Django 4.0 中的新功能。

Django 正在开发对异步缓存后端的支持,但还不支持异步缓存。它将在未来的版本中推出。

django.core.cache.backends.base.BaseCache具有所有基本方法的异步变体。按照惯例,所有方法的异步版本都以a. 默认情况下,两种变体的参数是相同的:

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

下游缓存

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

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

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

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

例如,如果您使用网络电子邮件系统,那么“收件箱”页面的内容取决于登录的用户。如果 ISP 盲目缓存您的站点,那么通过该 ISP 登录的第一个用户将拥有他们的用户- 为该站点的后续访问者缓存的特定收件箱页面。这不酷。

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

使用Vary标题

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

默认情况下,Django 的缓存系统使用请求的完全限定 URL 创建其缓存键——例如, "https://www.example.com/stories/2005/?order_by=author". 这意味着对该 URL 的每个请求都将使用相同的缓存版本,而不考虑用户代理的差异,例如 cookie 或语言首选项。但是,如果此页面基于请求标头中的某些差异(例如 cookie、语言或用户代理)产生不同的内容,您将需要使用Vary 标头告诉缓存机制页面输出取决于那些事物。

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

from django.views.decorators.vary import vary_on_headers

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

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

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

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

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

这告诉下游缓存在两者上有所不同,这意味着用户代理和 cookie 的每个组合都将获得自己的缓存值。例如,带有 user-agentMozilla和 cookie valuefoo=bar的请求将被认为与带有 user-agentMozilla和 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"

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

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

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

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

有关 Vary 标题的更多信息,请参阅官方变化规格

控制缓存:使用其他标头

缓存的其他问题是数据的隐私以及数据应该存储在级联缓存中的何处的问题。

用户通常面临两种缓存:他们自己的浏览器缓存(私有缓存)和他们的提供者的缓存(公共缓存)。公共缓存由多个用户使用并由其他人控制。这会给敏感数据带来问题——例如,您不希望将您的银行帐号存储在公共缓存中。因此,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 的服务器端缓存框架,您仍然可以使用最大年龄 指令:

from django.views.decorators.cache import cache_control

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

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

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

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

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

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

from django.views.decorators.cache import never_cache

@never_cache
def myview(request):
    ...

MIDDLEWARE的顺序

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

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

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

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

本文编辑:soogor
暂无评论,期待你的首评
Copyright © 2022 All Rights Reserved 山东上格信息科技有限公司 版权所有

鲁ICP备20007704号

Thanks for visiting my site.