本文翻译自django1.8.2的官方文档
一个动态站点最基本的特点是,嗯,他们是动态的.每次用户请求一个页面,WEB服务器做很多计算-从数据库查询到渲染模板到事务逻辑-到创建一个访问者看到的页面.从服务器开销来看,打开文件,读取,关闭,是非常耗资源的.
对于大多数的web应用来说,这些开销不是大问题.大多数web应用都不是sina.com或baidu.com;他们只是中小型应用,随便就好.但是对于中大型应用来说,尽可能的减少开销是非常必要的.
这就是缓存存在的理由.
将一些开销很大的计算的结果缓存起来,这样下次就不用再次计算了.下面是一些伪代码,解释了怎么生成动态网页.
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 genarated page in the cache(for next time)
return the generated page
django有一个强大的缓存系统,可以使你保存动态页面,而不用每个请求都计算.很方便地,django提供了多种不同级别的缓存粒度:你可以缓存特定的视图的输出,你可以只缓存很难生成的一些片段,或者你可以缓存整个站点.
django也能使用”downstream”缓存,Squid缓存和浏览器缓存.这些缓存你不能直接控制,但是可以通过HTTP头来指定站点的哪些数据要缓存,怎么缓存.
参考:
Cache Framework design philosophy解释了一些缓存框架用的设计决策.
缓存系统需要一些安装.也就是说,你得指定缓存数据存在哪里-文件系统,数据库或直接存在内存.这是影响缓存性能的一个重要决定,某些缓存类型比其他的快.
在settings文件里可以设置缓存,下面是所有缓存可用的解释:
最快的,最高效的缓存系统,django提供原生支持.完全的内存缓存,高负载.许多网站比如facebook,维基都在使用.
memcached在后台运行,指定内存大小.它提供增,查,改数据的高速接口.所有数据都存在内存, 没有文件系统和数据库开销.
在安装Memcached后,你还要安装Memcached库.都很多python的memcached库可用.最常用的2种是python-memcached和pylibmc.
在django中使用memcached:
- 设置BACKEND为django.core.cache.backends.memcached.MemcachedCache或django.core.cache.backends.memcached.PyLibMCCache(这取决于你使用哪个库)
- 设置LOACTION为ip:port值,ip和port分别是memcached运行的ip和port,或者unix:path值,path是memcached unix socket file的path.
下面的例子中,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 socket file /tmp/memcached.sock,使用python-memcached库
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': 'unix:/tmp/memcached.sock',
}
}
memcached一个优秀的特性是能在多个服务器上共享缓存.也就是说,你可以在多个机器上运行memcached,程序会把一组机器当成一个,不必再每台机器上都重复.
这就表示你可以在多个机器上运行Memcached守护进程,程序会将一组机器当作一个缓存对待,而不需要在每个机器上重复缓存值.为了使用这一高级特性,将所有服务器地址加入到LOCATION,用分号分隔或写成一个列表.
这个例子中,缓存分布在多个Memcached实例上,IP为172.19.26.240和172.19.26.242,端口都是11211:
CACHES = {
'default': {
'BACKEND': 'django.core.backends.memcached.MemcachedCache',
'LOCATION': [
'172.19.26.240:11211',
'172.19.26.242:11211',
]
}
}
在下面的例子中,缓存分布在多个Memcached实例上,IP为172.19.26.240 (port 11211), 172.19.26.242 (port 11212)和 172.19.26.244 (port 11213):
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缓存机制都不应该用于永久存储-它们设计为用于缓存,不是存储-我们在这里特别指明因为内存缓存是临时的.
django能存储缓存数据到数据库.如果你有一个快速,索引良好的数据库,它会运行很快.
为了使用数据库表作为你的缓存机制:
- 设置BACKEND为django.core.cache.backends.db.DatabaseCache
- 设置LOCATION为数据库表的名字.名字可以任意取,只要它有效并且不存在于你的数据库里.
这个例子中,缓存的表名是my_cache_table:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table',
}
}
在使用数据库缓存前,你必须用这个命令创建缓存表: python manage.py createcachetable
这会在你的数据库里创建表,用django的数据库缓存系统期望的合适的格式.表的名字从LOCATION来.
如果你使用多数据库缓存,createcachetable会为每个缓存创建一个表.
如果你使用多数据库,createcachetable会遵守你的database routers的allow_migrate()方法.
就像migrate, createcachetable一样,它不会碰已存在的表,它只会创建不存在的表.
django1.7更改:
在django1.7之前,createcachetable一次只创建一个表.你必须传递你要创建的表名作为参数,如果你使用多数据库,你必须使用–database选项.为了向下兼容,这仍然有效.
如果你使用多数据库缓存,你还需要安装routing instructions.为了路由,数据库缓存表会以一个名为CacheEntry的模型出现,在应用django_cache里.这个模型不会在模型缓存里出现,但是模型细节可以用于路由.
例如,下面的路由会将所有缓存读操作都指向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
If you don’t specify routing directions for the database cache model, the cache backend will use the default database.
Of course, if you don’t use the database cache backend, you don’t need to worry about providing routing instructions for the database cache model.
基于文件的缓存机制序列化和存储每个缓存值为单独的文件.先要设置BACKEND为’django.core.cache.backends.filebased.fileBasedCache’,LOCATION为合适的目录.例如,将缓存放在/var/tmp/django/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',
}
}
要使用绝对路径-就是说,必须从文件系统的根目录开始.结尾是否有斜杠都没关系.
确保指向的目录存在且可读可写.继续上面的例子,如果你的服务器以用户apache运行,确保目录/var/tmp/django_cache存在,且用户apache可读可写.
如果你没有在settings文件里指定其他的那么这个就是默认的缓存.如果你想要内存缓存的速度优势但不想运行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提供了一些开箱即用的缓存后端支持,有时你可能使用自定义的缓存后端(cache backend).为了使用django和外部的缓存后端,使用python导入路径为CACHES设置的BACKEND,就像这样:
CACHES = {
'default':{
'BACKEND': 'path.to.backend',
}
}
如果你正在构建自己的后端,你可以使用标准的缓存后端作为参考实现.你会在django源代码的django/core/cache/backends/目录中找到代码.
注意:没有任何有说服力的原因,例如主机不支持,你应该坚持使用django自带的缓存后端.他们经过良好测试且易于使用.
每个缓存后端都能指定额外的参数来控制缓存行为.这些参数作为CACHES设置中的额外的键.有效参数如下:
- TIMEOUT: 默认缓存超时时间,单位是秒,.这个参数默认是300秒(5分钟).
django1.7新增
你可以设置TIMEOUT为None,那样的话,缓存键永不过期.值0表示键马上过期(表示”不缓存”).
- OPTIONS: 任何传递给缓存后端的选项.每个后端的选项不同.由第三方库支持的缓存后端会直接将这些选项传递给底层缓存库.
Cache backends that implement their own culling strategy (i.e., the locmem, filesystem and database backends) will honor the following options:
- MAX_ENTRIES:缓存允许的最大条目数,在旧数据删除之前.这个参数默认是300.
- CULL_FREQUENCY:MAX_ENTRIES达到时,淘汰的条目比例.实际比例是 1 / CULL_FREQUENCY,所以设置CULL_FREQUENCY为2,当MAX_ENTRIES达到时会淘汰一半的条目.这个参数应该是整数默认为3.
CULL_FREQUENCY的值为0意味着当MAX_ENTRIES达到时整个缓存都会废弃.某些后端(特别是数据库)会在牺牲缓存命中率的情况下cull更快.
- KEY_PREFIX:一个字符串,django服务器使用,自动添加到所有的缓存键.
- VERSION:django服务器生成的缓存的默认版本号.
- KEY_FUNCTION:一个包含点号的指向一个函数的路径,定义怎么组合前缀,版本和键为最终的缓存key.
在这个例子中,文件系统后端设置为超时时间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_CLASSED设置,在这个例子中:
MIDDLEWARE_CLASSED = (
'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.cache.common,CommonMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
)
注意:
不,那不是排版错误:’update’中间件必须是清单中的第一个,且’fetch’中间件必须是最后一个.细节有点晦涩难懂,详情请看Order of MIDDLEWARE_CLASSES.
然后,添加下面的必需的设置到你的django设置文件:
- CACHE_MIDDLEWARE_ALIAS-用于存储的缓存别名.
- CACHE_MIDDLEWARE_SECONDS-每个页面的缓存秒数.
- CACHE_MIDDLEWARE_KEY_PREFIX-如果缓存由使用相同的django安装的跨站点分享,设置这个为站点的名字,或者其他的在这个django实例中唯一的字符串,来避免键冲突.如果你不在意就使用空字符串.
FetchFromCacheMiddleware缓存状态为200的,请求头和响应头允许的,GET和HEAD响应.同一个请求URL,查询参数不同,响应会被当成是唯一的页面,会单独缓存.This middleware expects that a HEAD request is answered with the same response headers as the corresponding GET request; in which case it can return a cached GET response for HEAD request.
另外,UpdateCacheMiddleware会自动在每个HttpResponse中设置一些头信息:
- 设置Last-Modified头为当刷新(非缓存)请求页面版本时的日期/时间.
- 设置Expries头为现在日期/时间加上定义的CACHE_MIDDLEWARE_SECONDS.
- 设置Cache-Control头来指定页面的最大age-还是使用CACHE_MIDDLEWARE_SECONDS设置.
如果一个视图有自己的缓存过期时间(例如,它的Cache-Control头有max-age部分),那么页面会缓存直到过期时间,而不是CACHE_MIDDLEWARE_SECONDS.使用django.views.decorators.cache中的装饰器你能很容易的设置视图的过期时间(使用cache_control装饰器)或视图不使用缓存(使用never_cache装饰器).
如果USE_I18N设置为True那么生成的缓存键会包含激活的语言名称-详情请看How Django discovers language perference.这就能使你很容易的缓存多国语言站点而不用自己创建缓存键.
当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 * 15是为了好读).60*15等于900-就是说15分钟乘以60秒每分钟.
视图级缓存和站点级缓存一样,是由URL来决定键的.如果多个URL指向同样的视图,每个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设置里的).默认情况下,使用默认的缓存,但是你也可以定制任何你希望的缓存:
@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和cache参数可以一起指定.key_prefix参数和CACHES里的KEY_PREFIX会连接到一起使用.
上一个部分中的例子,视图缓存使用了硬编码,因为cache_page更改了my_view方法.这将你的视图和缓存系统绑定到一起,不是好主意.例如,你可能想要在另外一个很少使用缓存的站点重用视图函数,或者你想将视图发布给另一个不使用缓存的人使用.这个问题的解决办法就是在URLconf里定制视图级缓存.
这很容易做到:在URLconf里引用视图时简单的包装下视图函数.下面的是之前的URLconf:
urlpatterns = [
url(r'^foo/([0-9]{1,2})/$', my_view),
]
下面是同样的事情,用cache_page包装了my_view:
from django.views.decorators.cache import cache_page:
urlpatterns = [
url(r'^foo/([0-9]{1,2})/$', cache_page(60 * 15)(my_view)),
]
有时候,缓存整个渲染页面不是很实用,且降低了便利性.
也许,例如,你的站点包含了一个视图,结果依赖于一些大开销查询,结果每隔一段时间都会有变动.在这种情况下,使用站点级或视图级缓存提供的整页缓存就不适用了,因为你不想缓存整个结果(因为有些数据经常变),但是你仍然想缓存一些很少改变的数据.
为了应对这种情况,django提供了一个简单的,底层的缓存API.你可以使用这些API来将对象存到缓存里,以任何你希望的粒度.你可以缓存任何pickled对象:字符串,字典,模型对象列表及更多.(大多数Python对象pickled;有关pickling的详情你可以查看python文档.)
>>> 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作为默认的有效的cache: >>> from django.core.cache import cache
这个对象等价于caches[‘default’].
- django.core.cache.get_cache(backend, **kwargs)
django1.7起不推荐使用:这个方法建议用caches替代
django1.7之前这个方法是获取缓存实例的标准方法.它同样可用于创建不同配置的新缓存实例.
>>> from django.core.cache import get_cache
>>> get_cache('default')
>>> get_cache('django.core.cache.backends.memcached.MemcachedCache', LOCATION='127.1.0.2')
>>> get_cache('default', TIMEOUT=300)
基本接口是set(key, value, timeout)和get(key):
>>> cache.set('my_key', 'hello, world!', 30)
>>> cache.get('my_key')
'hello, world!'
timeout参数是可选的默认是CACHES设置中的timeout参数.是缓存存储的秒数.传递None给timeout表示永久缓存.timeout为0表示不缓存.
如果缓存中对象不存在,cache.get()返回None:
# 等30秒,'my_key'过期...
>>> 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_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()会删除缓存里的所有东西,而不只是你的应用设置的键. >>> 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方法那么close()是空操作.
如果你在服务器间或在生产环境与开发环境之间共享缓存实例,可能一台服务器要使用另一台服务器的缓存.如果服务器上的缓存数据格式不一样,出了问题,很难诊断.
为了预防这种问题,django提供了给一个服务器的所有缓存键添加前缀的技术.当一个特定的缓存键在保存或查询时,django会自动的使用KEY_PREFIX缓存设置的值来给缓存键添加前缀.确保每个django实例有不同的KEY_PREFIX,这样就能肯定每个缓存值不会冲突.
当你更改了使用缓存值的正在运行中的代码,你需要清空所有的存在的缓存值.最简单的方式是刷新(flush)整个缓存,但这会造成仍然有效和有用的缓存值丢失.django提供了一个更好的方法来应对单个的缓存值.django的缓存框架有一个系统级的版本标识,来指定使用的VERSION缓存设置.这个设置的值会自动和缓存前缀,用户提供的缓存键组成最终的缓存键.
默认情况下,所有的键请求都会自动包含站点默认缓存键版本.但是原始的cache函数都包含version参数,所以你可以指定特定的缓存键版本来set或get.例如:
# 设置缓存键的版本是2
>>> cache.set('my_key', 'hello world!', version=2)
# 获取默认的版本(假设version=1)
>>> cache.get('my_key')
None
# 获取同一个键的版本2
>>> cache.get('my_key', version=2)
'hello world!'
特定的键的版本可以使用incr_version()和decr_version()方法来递增和递减.这就能使特定的键升到新版本,而不影响其他键.继续上一个例子:
# 递增'my_key'的版本
>>> cache.incr_version('my_key')
# 默认的版本还是无效
>>> cache.get('my_key')
None
# 版本2也无效
>>> cache.get('my_key', version=2)
None
# 但是版本3有效
>>> cache.get('my_key', version=3)
'hello world!'
正如前面2节所讲,由用户提供的缓存键不是一字不变的使用-它由缓存前缀和键版本组合成最终的缓存键.默认,这3个部分由冒号连接成最终的字符串:
def make_key(key, key_prefix, version):
return ':'.join(key_prefix, str(version), key)
如果你想用不同的方式组合,或者对最终键做其他处理(例如,对key部分加密),你可以提供自定义的函数.
KEY_FUNCTION缓存设置指定了一个带点号的路径,指向一个匹配make_key()原型的函数.如果有提供,就会使用自定义的函数代替默认的键组合函数.
Memcached,最常用的生产环境缓存后端,不允许缓存的键超过250字符,或包含空白符和控制字符(control characters),使用这些字符会导致异常.如果使用了这些键,memcached会出现错误,为了鼓励写出利于在不同缓存间移植的代码及减少不必要的错误,其他的内置缓存后端会发布一个警告(django.core.cache.backends.base.CacheKeyWarning).如果你在生产环境使用的后端能接受更大范围的键(自定义的后端,或内置的其他后端之一),并想使用更宽范围的字符,而不要警告,你可以在你的INSTALLED_APPS中的一个应用中的management模块里写上这些代码来关闭警告:
import warnings
from django.core.cache import CacheKeyWarning
warnings.simplefilter('ignore', CacheKeyWarning)
如果你想自定义内置后端之一的键检验逻辑,你可以继承它,重写validate_key方法,并遵照using a custom cache backend的指导.例如,你想修改locmen后端,将这些代码加入到模块里:
from django.core.cache.backends.locmem import LocMemCache
class CustomLocMemCache(LocMemCache):
def validate_key(self, key):
"""自定义检验逻辑,如有需要就抛出异常或警告."""
# ...
…并在你的CACHES设置的BACKEND部分使用带点号的Python路径指向这个class.
如果你使用缓存中间件,将每个部分放在MIDDLEWARE_CLASSED设置中正确的地方很重要.因为缓存中间件需要知道哪个头是哪个来区别缓存存储.中间件总是在响应头Vary上加上一些东西.
UpdateCacheMiddleware在响应阶段运行,在此阶段,中间件反着顺序运行,所以最上面的条目在响应阶段最后运行.因此,你需要确保UpdateCacheMiddleware在其他可能加点东西到Vary头的中间件之前出现.下面的中间件模块会做:
反之,FetchFromCacheMiddleware在请求阶段运行,在此期间,中间件按顺序运行,所以最上面的条目在请求阶段最先运行.FetchFromCacheMiddleware也需要在其他能改变Vary头的中间件之后运行,所以FetchFromCacheMiddleware必须排在这些条目后面.