有时候,缓存整个渲染页面并不会给你带来太多好处,事实上,这是不方便的矫枉过正。例如,也许您的网站包含一个视图,其结果取决于几个昂贵的查询,其结果会以不同的时间间隔进行更改。在这种情况下,使用每个站点或每个视图缓存策略提供的整页缓存并不理想,因为您不希望缓存整个结果(因为一些数据经常变化),但你仍然想缓存很少改变的结果。
对于这种情况,Django公开了一个简单的低级缓存API。您可以使用此API将对象以任意级别的粒度存储在缓存中。您可以缓存任何可以安全腌制的Python对象:字符串,字典,模型对象列表等等。 (大多数常见的Python对象都可以被腌制;有关腌制的更多信息,请参阅Python文档。)
访问缓存
您可以通过类似字典的对象访问在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:
>>> 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!'
timeout参数是可选的,并且默认为CACHES设置中相应后端的超时参数(如上所述)。 这是值应该存储在缓存中的秒数。 传入无超时将永远缓存该值。 超时0不会缓存该值。 如果该对象不存在于缓存中,则cache.get()返回None:
# Wait 30 seconds for 'my_key' to expire...
>>> cache.get('my_key')
None
我们建议不要在缓存中存储文字值None,因为您无法区分存储的None值和由返回值None表示的缓存未命中。 cache.get()可以采用默认参数。 这指定了如果对象在缓存中不存在时要返回哪个值:
>>> 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_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()接受一个可选的超时参数。 您可以使用delete()显式删除键。 这是清除特定对象缓存的简单方法:
>>> cache.delete('a')
如果您想要一次清除一堆密钥,delete_many()可以清除要清除的密钥列表:
>>> cache.delete_many(['a', 'b', 'c'])
最后,如果要删除缓存中的所有密钥,请使用cache.clear()。 小心这个; clear()将从缓存中删除所有内容,而不仅仅是应用程序设置的密钥。
>>> 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
如果由缓存后端实现,可以使用close()关闭与缓存的连接。
>>> cache.close()
请注意,对于不执行关闭方法的缓存,close()是无操作的。
缓存键前缀
如果您在服务器之间或生产环境与开发环境之间共享缓存实例,则可能由另一台服务器使用一台服务器缓存的数据。如果服务器之间缓存数据的格式不同,这可能会导致一些非常难以诊断的问题。
为了防止出现这种情况,Django提供了为服务器使用的所有缓存密钥加前缀的功能。当保存或检索特定的缓存键时,Django会自动为缓存键添加KEY_PREFIX缓存设置的值。通过确保每个Django实例具有不同的KEY_PREFIX,可以确保缓存值中不会发生冲突。
缓存版本控制
当您更改使用缓存值的运行代码时,您可能需要清除任何现有的缓存值。最简单的方法是刷新整个缓存,但这会导致丢失仍然有效且有效的缓存值。 Django提供了一种更好的方法来定位各个缓存值。
Django的缓存框架有一个系统范围的版本标识符,使用VERSION缓存设置进行指定。此设置的值将自动与缓存前缀和用户提供的缓存键组合,以获取最终的缓存键。
默认情况下,任何密钥请求都会自动包含网站默认缓存密钥版本。但是,原始缓存函数都包含版本参数,因此您可以指定要设置或获取的特定缓存密钥版本。例如:
# Set version 2 of a cache key
>>> cache.set('my_key', 'hello world!', version=2)
# Get the default version (assuming version=1)
>>> cache.get('my_key')
None
# Get version 2 of the same key
>>> cache.get('my_key', version=2)
'hello world!'
使用incr_version()和decr_version()方法可以递增和递减特定键的版本。 这使得特定的密钥可以被碰撞到新的版本,而不会影响其他密钥。 继续我们之前的例子:
# Increment the version of 'my_key'
>>> cache.incr_version('my_key')
# The default version still isn't available
>>> cache.get('my_key')
None
# Version 2 isn't available, either
>>> cache.get('my_key', version=2)
None
# But version 3 *is* available
>>> cache.get('my_key', version=3)
'hello world!'
缓存密钥转换
如前两节所述,用户提供的缓存密钥不是逐字使用的 - 它与缓存前缀和密钥版本结合使用以提供最终的缓存密钥。 默认情况下,这三个部分使用冒号进行连接以产生最终字符串:
def make_key(key, key_prefix, version):
return ':'.join([key_prefix, str(version), key])
如果您想以不同的方式组合各个部分,或将其他处理应用于最终键(例如,对关键部分进行散列摘要),则可以提供自定义键功能。 KEY_FUNCTION缓存设置指定了与上面make_key()的原型匹配的函数的虚线路径。如果提供,
这个自定义按键功能将被用来代替默认按键组合功能。
缓存键警告
Memcached是最常用的生产缓存后端,它不允许长度超过250个字符的缓存键或包含空格或控制字符的缓存键,并且使用这些键会导致异常。为了鼓励缓存可移植代码并尽量减少不愉快的意外,如果使用的密钥会导致memcached发生错误,其他内置缓存后端会发出警告(django.core.cache.backends.base.CacheKeyWarning)。
如果您使用的产品后端可以接受更广泛的键(自定义后端或非memcached内置后端之一),并且希望在不发出任何警告的情况下使用更广泛的范围,则可以使用此代码使CacheKeyWarning无效在你的一个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.
# ...
...并在您的CACHES设置的BACKEND部分使用虚线Python路径指向此类。
下游高速缓存
到目前为止,本章着重于缓存自己的数据。 但另一种类型的缓存也与Web开发相关:由下游缓存执行缓存。 这些系统甚至在请求到达您的网站之前为用户缓存页面。 下面是一些下游缓存的例子:
-
您的ISP可能会缓存某些页面,因此如果您从http://example.com/请求了一个页面,
您的ISP会将您的页面发送给您,而无需直接访问example.com。
example.com的维护者不知道这种缓存; ISP位于example.com和您的Web浏览器之间,透明地处理所有缓存。 -
您的Django网站可能位于代理缓存后面,例如Squid Web Proxy Cache,
缓存页面的性能。 在这种情况下,每个请求将首先由代理处理,并且仅在需要时才会传递给您的应用程序。 -
您的Web浏览器也会缓存页面。 如果网页发出适当的标题,则浏览器将使用本地缓存副本来处理对该页面的后续请求,而无需再次联系网页以查看它是否已更改。
下游缓存提高了效率,但存在一个危险:很多网页的内容因认证和其他一些变量而不同,并且缓存系统会盲目保存基于URL的页面,从而可能会将不正确或敏感的数据暴露给后续这些网页的访问者。
例如,假设您使用Web电子邮件系统,并且收件箱页面的内容显然取决于哪个用户已登录。如果ISP盲目地缓存您的站点,那么通过该ISP登录的第一个用户将拥有他们的用户 - 为该网站的后续访问者缓存特定收件箱页面。这并不酷。
幸运的是,HTTP为这个问题提供了一个解决方案。存在许多HTTP标头以指示下游高速缓存根据指定的变量来区分它们的高速缓存内容,并且指示高速缓存机构不高速缓存特定页面。我们将在后面的章节中查看一些这些标题。
使用可变标题
Vary头部定义了缓存机制在构建缓存密钥时应该考虑哪些请求头部。例如,如果网页的内容取决于用户的语言偏好,则该网页据说因语言而异。默认情况下,Django的缓存系统使用请求的完全限定URL创建缓存密钥 - 例如http://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 ['Vary'] ='user-agent'”)的优点是装饰器添加到Vary头部(可能已经存在),而是 比从头开始设置它可能会覆盖已经存在的任何东西。 您可以将多个标头传递给vary_on_headers():
@vary_on_headers('User-Agent', 'Cookie')
def my_view(request):
# ...
这告诉下游高速缓存在两者上有所不同,
这意味着用户代理和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标题。 例如:
from django.utils.cache import patch_vary_headers
def my_view(request):
# ...
response = render_to_response('template_name', context)
patch_vary_headers(response, ['Cookie'])
return response
patch_vary_headers将HttpResponse实例作为其第一个参数,并将大小写不敏感的标头名称的列表/元组作为其第二个参数。 有关Vary标题的更多信息,请参阅官方的Vary规范。
控制缓存:使用其他页眉
高速缓存的其他问题是数据的隐私性以及数据应该存储在缓存级联中的问题。 用户通常面对两种缓存:他们自己的浏览器缓存(私有缓存)和他们的提供者缓存(公共缓存)。
公用缓存由多个用户使用并由其他人控制。 这会对敏感数据造成问题 - 您不希望存储在公共缓存中的银行帐号。 因此,Web应用程序需要一种方法来告诉缓存哪些数据是私有的,哪些是公共的。
解决方案是指示页面的缓存应该是私有的。 要在Django中执行此操作,请使用cache_control视图修饰器。 例:
from django.views.decorators.cache import cache_control
@cache_control(private=True)
def my_view(request):
# ...
这个装饰器负责在幕后发送适当的HTTP头。 请注意,私有和公共的缓存控制设置是互斥的。 装饰者确保公共指令被删除,如果私人应该被设置(反之亦然)。
这两个指令的一个例子是一个提供私有和公共条目的博客网站。 公共条目可以缓存在任何共享缓存上。 以下代码使用django.utils.cache.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允许应用程序执行以下操作:
- 定义页面应该被缓存的最大时间。
- 指定缓存是否应该始终检查更新的版本,只在没有更改时传递缓存的内容。 (即使服务器页面已更改,某些缓存可能会传递缓存的内容,只是因为缓存副本尚未过期。)
在Django中,使用cache_control视图装饰器来指定这些缓存参数。 在此示例中,cache_control通知缓存在每次访问时重新验证缓存,并存储最多3,600秒的缓存版本:
from django.views.decorators.cache import cache_control
@cache_control(must_revalidate=True, max_age=3600)
def my_view(request):
# ...
任何有效的Cache-Control HTTP指令在cache_control()中都有效。 这是一个完整的列表:
- public=True
- private=True
- no_cache=True
- no_transform=True
- must_revalidate=True
- proxy_revalidate=True
- max_age=num_seconds
- s_maxage=num_seconds
有关缓存控制HTTP指令的说明,请参阅缓存控制规范。 (请注意,缓存中间件已经使用CACHE_MIDDLEWARE_SECONDS设置的值设置了缓存头的max-age,如果在cache_control装饰器中使用自定义max_age,则装饰器将优先,并且标头值将被正确合并。
如果你想使用头来完全禁用缓存,django.views.decorators.cache.never_cache是一个视图装饰器,它添加了头部以确保响应不会被浏览器或其他缓存缓存。 例如:
from django.views.decorators.cache import never_cache
@never_cache
def myview(request):
# ...
下一步是什么?
在下一章中,我们将讨论Django的中间件。
网友评论