> Django中文手册 > Django 缓存机制

动态网站的问题就在于它是动态的。 也就是说每次用户访问一个页面,服务器要执行数据库查询,启动模板,执行业务逻辑以及最终生成一个你所看到的网页,这一切都是动态即时生成的。 从处理器资源的角度来看,这是比较昂贵的。

对于大多数网络应用来说,过载并不是大问题。 因为大多数网络应用并不是washingtonpost.com或Slashdot;它们通常是很小很简单,或者是中等规模的站点,只有很少的流量。 但是对于中等至大规模流量的站点来说,尽可能地解决过载问题是非常必要的。

这就需要用到缓存了。

缓存的目的是为了避免重复计算,特别是对一些比较耗时间、资源的计算。 下面的伪代码演示了如何对动态页面的结果进行缓存。

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://danga.com/memcached)。它作为一个守护进程运行,并分配了特定数量的内存。 它只是提供了添加,检索和删除缓存中的任意数据的高速接口。 所有数据都直接存储在内存中,所以没有对使用的数据库或文件系统的开销。

在安装了Memcached本身之后,你将需要安装Memcached Python绑定,它没有直接和Django绑定。 这两个可用版本。 选择和安装以下模块之一:

  • 最快的可用选项是一个模块,称为cmemcache,在http://gijsbert.org/cmemcache。

  • 如果您无法安装cmemcache,您可以安装python - Memcached,在ftp://ftp.tummy.com/pub/python-memcached/。如果该网址已不再有效,只要到Memcached的网站http://www.danga.com/memcached/),并从客户端API完成Python绑定。

若要使用Memcached的Django,设置CACHE_BACKEND到memcached:/ / IP:port/,其中IP是Memcached的守护进程的IP地址,port是Memcached运行的端口。

在这个例子中,Memcached运行在本地主机 (127.0.0.1)上,端口为11211:

CACHE_BACKEND = 'memcached://127.0.0.1:11211/'

Memcached的一个极好的特性是它在多个服务器间分享缓存的能力。 这意味着您可以在多台机器上运行Memcached的守护进程,该程序会把这些机器当成一个单一缓存,而无需重复每台机器上的缓存值。 要充分利用此功能,请在CACHE_BACKEND里引入所有服务器的地址,用分号分隔。

这个例子中,缓存在运行在IP地址为172.19.26.240和172.19.26.242,端口号为11211的Memcached实例间分享:

CACHE_BACKEND = 'memcached://172.19.26.240:11211;172.19.26.242:11211/'

这个例子中,缓存在运行在172.19.26.240(端口11211),172.19.26.242(端口11212),172.19.26.244(端口11213)的Memcached实例间分享:

CACHE_BACKEND = 'memcached://172.19.26.240:11211;172.19.26.242:11212;172.19.26.244:11213/'

最后有关Memcached的一点是,基于内存的缓存有一个重大的缺点。 由于缓存的数据存储在内存中,所以如果您的服务器崩溃,数据将会消失。 显然,内存不是用来持久化数据的,因此不要把基于内存的缓存作为您唯一的存储数据缓存。 毫无疑问,在Django的缓存后端不应该用于持久化,它们本来就被设计成缓存的解决方案。但我们仍然指出此点,这里是因为基于内存的缓存是暂时的。

数据库缓存

为了使用数据库表作为缓存后端,首先在数据库中运行这个命令以创建缓存表:

python manage.py createcachetable [cache_table_name]

这里的[cache_table_name]是要创建的数据库表名。 (这个名字随你的便,只要它是一个有效的表名,而且不是已经在您的数据库中使用的表名。)这个命令以Django的数据库缓存系统所期望的格式创建一个表。

一旦你创建了数据库表,把你的CACHE_BACKEND设置为”db://tablename”,这里的tablename是数据库表的名字,在这个例子中,缓存表名为my_cache_table: 在这个例子中,高速缓存表的名字是my_cache_table:

CACHE_BACKEND = 'db://my_cache_table'

数据库缓存后端使用你的settings文件指定的同一数据库。 你不能为你的缓存表使用不同的数据库后端.

如果你已经有了一个快速,良好的索引数据库服务器,那么数据库缓存的效果最明显。

文件系统缓存

要把缓存项目放在文件系统上,请为CACHE_BACKEND使用”http://example.com/ 请求一个页面,你的 ISP 可能无需直接访问 example.com 就能将页面发送给你。 而 example.com 的维护者们却无从得知这种缓存,ISP 位于 example.com 和你的网页浏览器之间,透明地处理所有的缓存。

  • 你的 Django 网站可能位于某个 代理缓存 之后,例如 Squid 网页代理缓存 (http://www.squid-cache.org/),该缓存为提高性能而对页面进行缓存。 在此情况下 ,每个请求将首先由代理服务器进行处理,然后仅在需要的情况下才被传递至你的应用程序。

  • 你的网页浏览器也对页面进行缓存。 如果某网页送出了相应的头部,你的浏览器将在为对该网页的后续的访问请求使用本地缓存的拷贝,甚至不会再次联系该网页查看是否发生了变化。
  • 上游缓存将会产生非常明显的效率提升,但也存在一定风险。 许多网页的内容依据身份验证以及许多其他变量的情况发生变化,缓存系统仅盲目地根据 URL 保存页面,可能会向这些页面的后续访问者暴露不正确或者敏感的数据。

    举个例子,假定你在使用网页电邮系统,显然收件箱页面的内容取决于登录的是哪个用户。 如果 ISP 盲目地缓存了该站点,那么第一个用户通过该 ISP 登录之后,他(或她)的用户收件箱页面将会缓存给后续的访问者。 这一点也不好玩。

    幸运的是, HTTP 提供了解决该问题的方案。 已有一些 HTTP 头标用于指引上游缓存根据指定变量来区分缓存内容,并通知缓存机制不对特定页面进行缓存。 我们将在本节后续部分将对这些头标进行阐述。

    使用 Vary头部

    Vary 头部定义了缓存机制在构建其缓存键值时应当将哪个请求头标考虑在内。 例如,如果网页的内容取决于用户的语言偏好,该页面被称为根据语言而不同。

    缺省情况下,Django 的缓存系统使用所请求的路径(比如:"/stories/2005/jun/23/bank_robbed/" )来创建其缓存键。这意味着每次请求都会使用同样的缓存版本,不考虑才客户端cookie和语言配置的不同。 除非你使用Vary头部通知缓存机制页面输出要依据请求头里的cookie,语言等的设置而不同。

    要在 Django 完成这项工作,可使用便利的 vary_on_headers 视图装饰器,如下所示:

    from django.views.decorators.vary import vary_on_headers
    
    # Python 2.3 syntax.
    def my_view(request):
        # ...
    my_view = vary_on_headers(my_view, 'User-Agent')
    
    # Python 2.4+ decorator syntax.
    @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):
        # ...

    该段代码通知上游缓存对 两者 都进行不同操作,也就是说 user-agent 和 cookie 的每种组合都应获取自己的缓存值。 举例来说,使用 Mozilla 作为 user-agent 而 foo=bar 作为 cookie 值的请求应该和使用 Mozilla 作为 user-agent 而 foo=ham 的请求应该被视为不同请求。

    由于根据 cookie 而区分对待是很常见的情况,因此有 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.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 实例为第一个参数,以一个大小写不敏感的头标名称列表或元组为第二个参数。

    控制缓存: 使用其它头部

    关于缓存剩下的问题是数据的隐私性以及在级联缓存中数据应该在何处储存的问题。

    通常用户将会面对两种缓存: 他或她自己的浏览器缓存(私有缓存)以及他或她的提供者缓存(公共缓存)。 公共缓存由多个用户使用,而受其他某人的控制。 这就产生了你不想遇到的敏感数据的问题,比如说你的银行账号被存储在公众缓存中。 因此,Web 应用程序需要以某种方式告诉缓存那些数据是私有的,哪些是公共的。

    解决方案是标示出某个页面缓存应当是私有的。 要在 Django 中完成此项工作,可使用 cache_control 视图修饰器: 例如:

    from django.views.decorators.cache import cache_control
    
    @cache_control(private=True)
    def my_view(request):
        # ...

    该修饰器负责在后台发送相应的 HTTP 头部。

    还有一些其他方法可以控制缓存参数。 例如, HTTP 允许应用程序执行如下操作:

    • 定义页面可以被缓存的最大时间。

    • 指定某个缓存是否总是检查较新版本,仅当无更新时才传递所缓存内容。 (一些缓存即便在服务器页面发生变化的情况下仍然会传送所缓存的内容,只因为缓存拷贝没有过期。)

    在 Django 中,可使用 cache_control 视图修饰器指定这些缓存参数。 在本例中, cache_control 告诉缓存对每次访问都重新验证缓存并在最长 3600 秒内保存所缓存版本:

    from django.views.decorators.cache import cache_control
    
    @cache_control(must_revalidate=True, max_age=3600)
    def my_view(request):
        # ...

    在 cache_control() 中,任何合法的Cache-Control HTTP 指令都是有效的。下面是完整列表:

    • public=True

    • private=True

    • no_cache=True

    • no_transform=True

    • must_revalidate=True

    • proxy_revalidate=True

    • max_age=num_seconds

    • s_maxage=num_seconds

    缓存中间件已经使用 CACHE_MIDDLEWARE_SETTINGS 设置设定了缓存头部 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 带有一些其它中间件可帮助您优化应用程序的性能:

    • django.middleware.http.ConditionalGetMiddleware 为现代浏览器增加了有条件的,基于 ETag 和Last-Modified 头标的GET响应的相关支持。

    • django.middleware.gzip.GZipMiddleware 为所有现代浏览器压缩响应内容,以节省带宽和传送时间。

    MIDDLEWARE_CLASSES 的顺序

    如果使用缓存中间件,注意在MIDDLEWARE_CLASSES设置中正确配置。 因为缓存中间件需要知道哪些头部信息由哪些缓存区来区分。 中间件总是尽可能得想Vary响应头中添加信息。

    UpdateCacheMiddleware在相应阶段运行。因为中间件是以相反顺序运行的,所有列表顶部的中间件反而_last_在相应阶段的最后运行。 所有,你需要确保UpdateCacheMiddleware排在任何可能往_Vary_头部添加信息的中间件之前。 下面的中间件模块就是这样的:

    • 添加 Cookie 的 SessionMiddleware

    • 添加 Accept-Encoding 的 GZipMiddleware

    • 添加Accept-Language的LocaleMiddleware

    另一方面,FetchFromCacheMiddleware在请求阶段运行,这时中间件循序执行,所以列表顶端的项目会_首先_执行。 FetchFromCacheMiddleware也需要在会修改Vary头部的中间件之后运行,所以FetchFromCacheMiddleware必须放在它们后面

    下一章

    Django捆绑了一系列可选的方便特性。 我们已经介绍了一些: admin站点(第六章)和session/user框架(第十四章)。 下一章中,我们将讲述Django中其他的子框架。