ホームページ >バックエンド開発 >Python チュートリアル >Django のキャッシュメカニズム
静的 Web サイトのコンテンツはサーバーに直接保存された単純な静的 Web ページであり、驚くべき数のアクセスを簡単に達成できます。しかし、動的 Web サイトは動的です。つまり、ユーザーがページにアクセスするたびに、サーバーはデータベース クエリを実行し、テンプレートを開始し、ビジネス ロジックを実行し、最終的に見たと思われる Web ページを生成する必要があります。これらはすべて動的に生成されます。即座に。プロセッサ リソースの観点から見ると、これは比較的高価です。
ほとんどの Web アプリケーションでは、過負荷は大きな問題ではありません。ほとんどの Web アプリケーションは washingtopost.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 はさまざまな粒度でのデータのキャッシュも提供します。たとえば、ページ全体、特定の部分、さらには Web サイト全体をキャッシュできます。
Django は、Squid (http://www.squid-cache.org) やブラウザベースのキャッシュなどの「アップストリーム」キャッシュでもうまく機能します。これらのタイプのキャッシュはユーザーによって直接制御されません。サイトのどの部分をどのようにキャッシュする必要があるかに関するヒントを (HTTP ヘッダー経由で) 提供します
Django のキャッシュ システムの使用方法については、以下をお読みください。あなたのサイトが Slashdot のようなサイトになったとき、このことを学べたことをうれしく思うでしょう
キャッシュのセットアップ
キャッシュ システムには、いくつかの簡単なセットアップ作業が必要です。つまり、キャッシュされたデータがどこにあるかをキャッシュ システムに指示する必要があります。はい、一部のキャッシュ タイプは、通常、他のキャッシュ タイプよりも高速です。前者はファイル システムにアクセスしたり、データベースへのオーバーザトップ接続を行わないためです
キャッシュを使用するが CACHE_BACKEND を指定しない場合、キャッシュの選択は設定ファイルの CACHE_BACKEND 設定にあります。 Django はデフォルトで simple:/// になります。 CACHE_BACKEND の利用可能な値はすべて以下で説明されています
メモリ バッファリング
Django で利用できるキャッシュの最も高速かつ効率的なタイプは、メモリベースのキャッシュです。キャッシュ フレームワーク Memcached は、もともと LiveJournal .com 用に開発され、高負荷を処理しますが、その後 Danga
Interactive (http://www.danga.com) によってオープンソース化されました。これは、Slashdot や Wikipedia などのサイトでデータベース アクセスを削減するために使用され、サイトのパフォーマンスを劇的に向上させます
Memcached は http://danga.com/memcached/ で無料で利用でき、バックグラウンド プロセスとして実行され、指定された量の RAM を割り当てて *超高速* に追加します。キャッシュ内の任意のデータを取得および削除します。すべてのデータはメモリに直接保存されるため、データベースやファイル システムの過剰使用はありません
Memcached 自体をインストールした後、MemcachedPython バインディングをインストールする必要があります。 Django との直接バインディングがあり、これらのバインディングは、http://www.djangoproject.com/thirdparty/python-memcached で入手可能な別の Python モジュール「memcache.py」で
CACHE_BACKEND を memcached://ip に設定します:port/ は、Django が Memcached を使用できるようにします。ip は Memcached バックグラウンド プロセスの IP アドレス、port は Memcached が実行されているポートです。この例では、Memcached はローカル ホスト (127.0.0.1) で実行されています。ポートは 11211:
CACHE_BACKEND = 'memcached://127.0.0.1:11211/'
この例では、キャッシュは IP アドレス 172.19.26.240 および 172.19.26.242 とポート 11211 で実行されている Memcached インスタンス間で共有されます:
CACHE_BACKEND = 'memcached://172.19.26.240:11211 ;172.19.26.242 :11211 / '
Memcached に関する最後の点は、メモリベースのキャッシュには大きな欠点があるということです。キャッシュされたデータはメモリにのみ保存されるため、サーバーがクラッシュするとデータは失われます。明らかに、メモリは永続的なデータ ストレージ用に準備されていませんが、Django には用意されています。キャッシュ バックエンドがありません。これらはストレージではなくキャッシュ ソリューションです。ただし、キャッシュ バックエンドとしては、キャッシュ テーブルを作成する必要があるため、ここで注意します。データベースを作成し、Django のキャッシュ システムをテーブルにポイントします
[cache_table_name] ここにデータベース テーブルの名前を指定します。このコマンドは、データベース内で Django に従う別のテーブルを作成します。
データベーステーブルを作成したら、CACHE_BACKEND 設定を「db://tablename」に設定します。tablename はデータベーステーブルの名前で、この例ではキャッシュテーブル名です。 my_cache_table:
ファイル システム キャッシュ
を使用しますfile://" キャッシュ タイプを CACHE_BACKEND として指定し、キャッシュ データをファイル システムに保存するには、キャッシュ データが保存されているファイル システム ディレクトリを指定します。
たとえば、キャッシュ データを /var / に保存するには、次の設定を使用します。 tmp/django_cache はキャッシュ データを保存します:
CACHE_BACKEND = 'file:///var/tmp/django_cache'
例の先頭に 3 つのスラッシュがあることに注意してください。最初の 2 つは file://、 3 番目はディレクトリです。パスの最初の文字 /var/tmp/django_cache。Windows システムを使用している場合は、「file://c:/foo/bar」のように、file:// の後にドライブ文字を置きます。 .
ディレクトリ パスは *絶対* パスである必要があります。つまり、ファイル システムのルートから開始する必要があり、設定の最後にスラッシュを付けるかどうかは関係ありません
設定によって指定されたディレクトリが存在し、Web サーバーが実行されているシステムのユーザーがそのディレクトリを読み取りおよび書き込みできることを確認します。サーバーがユーザー Apache として実行されている場合は、/ を確認します。 var/tmp/django_cache が存在し、そのユーザー apache は /var/tmp/django_cache ディレクトリを読み書きできます
各キャッシュ値は別個のファイルとして保存され、その内容はシリアル化された形式で保存されたキャッシュ データです ("pickled ") Python の pickle モジュールによる形式であり、各ファイルのファイル名はセキュリティのためのキャッシュ キーです。 ファイル システムの使用量を解放します
ローカル メモリ キャッシュ
memcaching の速度上のメリットが欲しいが、そうでない場合Memcached を実行する機能はありません。マルチスレッドでスレッドセーフなローカル メモリ キャッシュ バックエンドの使用を検討してください。ただし、単純なロックとメモリ割り当て戦略のため、Memcached ほど効率的ではありません
単純なキャッシュ (開発段階用)
単純な単一キャッシュを使用できます- 'simple:///' を設定してプロセス メモリ キャッシュを実行します。例:
このキャッシュはプロセス内のデータを保存するだけなので、開発またはテストでのみ使用する必要があります。
偽のキャッシュ (開発用)
CACHE_BACKEND = 'dummy:///'
結果は、開発環境ではキャッシュを使用しませんが、オンライン環境では引き続きキャッシュを使用します
CACHE_BACKEND パラメーター
各キャッシュ バックエンドは、 CACHE_BACKEND 設定のクエリ文字列の形式で指定されるパラメーターを使用できます。有効なパラメーターは次のとおりです:
timeout: キャッシュの有効期限 (秒単位)。このパラメータはデフォルトで 300 秒 (5 分) に設定されています。
max_entries: 単純なローカル メモリおよびデータベース タイプのキャッシュの場合、このパラメータは、キャッシュに保存されるエントリの最大数を指定します (これより大きい場合)。番号、古いエントリは削除されます。このパラメータのデフォルト値は 300 です。
cull_frequency: max_entries に達したときの、受け入れられるアクセスの割合。実際の比率は 1/cull_frequency なので、cull_frequency=2 に設定すると、max_entries に達したときにキャッシュの量の半分が削除されます
cull_frequency の値を 0 に設定すると、max_entries に達するとキャッシュがクリアされます。これにより、多くのキャッシュ ミスが発生しますが、アクセスの受信速度が大幅に向上します。この値のデフォルトは 3 です
この例では、タイムアウトは 60 に設定されています
CACHE_BACKEND = "locmem:///?timeout=60"
而在这个例子中,timeout设为30而max_entries为400:
CACHE_BACKEND = "locmem:///?timeout=30&max_entries=400"
其中,非法的参数与非法的参数值都将被忽略。
站点级 Cache
一旦你指定了”CACHE_BACKEND”,使用缓存的最简单的方法就是缓存你的整个网站。这意味着所有不包含GET或POST参数的页面在第一次被请求之后将被缓存指定好的一段时间。
要激活每个站点的cache,只要将``’django.middleware.cache.CacheMiddleware’``添加到MIDDLEWARE_CLASSES的设置里,就像下面这样:
MIDDLEWARE_CLASSES = ( 'django.middleware.cache.CacheMiddleware', 'django.middleware.common.CommonMiddleware', )
注意
关于MIDDLEWARE_CLASSES顺序的一些事情。请看本章节后面的MIDDLEWARE_CLASSES顺序部分。
然后,在你的Django settings文件里加入下面所需的设置:
CACHE_MIDDLEWARE_SECONDS:每个页面应该被缓存的秒数
§ “CACHE_MIDDLEWARE_KEY_PREFIX”:如果缓存被多个使用相同Django安装的网站所共享,那么把这个值设成当前网站名,或其他能代表这个Django实例的唯一字符串,以避免key发生冲突。如果你不在意的话可以设成空字符串。
缓存中间件缓存每个没有GET或者POST参数的页面,即如果用户请求页面并在查询字符串里传递GET参数或者POST参数,中间件将不会尝试得到缓存版本的页面,如果你打算使用整站缓存,设计你的程序时牢记这点,例如,不要使用拥有查询字符串的URLs,除非那些页面可以不缓存
缓存中间件( cache middleware)支持另外一种设置选项,CACHE_MIDDLEWARE_ANONYMOUS_ONLY。如果你把它设置为“True”,那么缓存中间件就只会对匿名请求进行缓存,匿名请求是指那些没有登录的用户发起的请求。如果想取消用户相关页面(user-specific
pages)的缓存,例如Djangos的管理界面,这是一种既简单又有效的方法。另外,如果你要使用CACHE_MIDDLEWARE_ANONYMOUS_ONLY选项,你必须先激活AuthenticationMiddleware才行,也就是在你的配置文件MIDDLEWARE_CLASSES的地方,AuthenticationMiddleware必须出现在CacheMiddleware前面。
最后,再提醒一下:CacheMiddleware在每个HttpResponse中都会自动设置一些头部信息(headers)
§ 当一个新(没缓存的)版本的页面被请求时设置Last-Modified头部为当前日期/时间
§ 设置Expires头部为当前日期/时间加上定义的CACHE_MIDDLEWARE_SECONDS
§ 设置Cache-Control头部来给页面一个最大的时间—再一次,根据CACHE_MIDDLEWARE_SECONDS设置
视图级缓存
更加颗粒级的缓存框架使用方法是对单个视图的输出进行缓存。这和整站级缓存有一样的效果(包括忽略对有 GET和 POST
参数的请求的缓存)。它应用于你所指定的视图,而不是整个站点。
完成这项工作的方式是使用修饰器,其作用是包裹视图函数,将其行为转换为使用缓存。视图缓存修饰器称为cache_page,位于django.views.decorators.cache模块中,例如:
from django.views.decorators.cache import cache_page def my_view(request, param): # ... my_view = cache_page(my_view, 60 * 15)
如果使用 Python 2.4或更高版本,
你也可以使用 decorator语法。这个例子和前面的那个是等同的:
from django.views.decorators.cache import cache_page @cache_page(60 * 15) def my_view(request, param): # ...
cache_page只接受一个参数:以秒计的缓存超时。在前例中, “my_view()”视图的结果将被缓存 15
分钟。(注意:为了提高可读性,该参数被书写为60 * 15。60 * 15将被计算为900,也就是说15分钟乘以每分钟
60 秒。)
和站点缓存一样,视图缓存与 URL无关。如果多个 URL
指向同一视图,每个视图将会分别缓存。继续my_view范例,如果 URLconf如下所示:
urlpatterns = ('', (r'^foo/(/d{1,2})/$', my_view), )
那么正如你所期待的那样,发送到/foo/1/和/foo/23/的请求将会分别缓存。但一旦发出了特定的请求(如:/foo/23/),之后再度发出的指向该
URL 的请求将使用缓存。
在 URLconf中指定视图缓存
前一节中的范例将视图硬编码为使用缓存,因为cache_page在适当的位置对my_view函数进行了转换。该方法将视图与缓存系统进行了耦合,从几个方面来说并不理想。例如,你可能想在某个无缓存的站点中重用该视图函数,或者你可能想将该视图发布给那些不想通过缓存使用它们的人。解决这些问题的方法是在
URLconf 中指定视图缓存,而不是紧挨着这些视图函数本身来指定。
完成这项工作非常简单:在 URLconf中用到这些视图函数的时候简单地包裹一个cache_page。以下是刚才用到过的
URLconf : urlpatterns = ('', (r'^foo/(/d{1,2})/$', my_view), ) 以下是同一个 URLconf,不过用cache_page包裹了my_view: from django.views.decorators.cache import cache_page urlpatterns = ('', (r'^foo/(/d{1,2})/$', cache_page(my_view, 60 * 15)), )
如果采取这种方法,不要忘记在 URLconf
中导入cache_page.
低层次缓存API
有些时候,对整个经解析的页面进行缓存并不会给你带来太多,事实上可能会过犹不及。
比如说,也许你的站点所包含的一个视图依赖几个费时的查询,每隔一段时间结果就会发生变化。在这种情况下,使用站点级缓存或者视图级缓存策略所提供的整页缓存并不是最理想的,因为你可能不会想对整个结果进行缓存(因为一些数据经常变化),但你仍然会想对很少变化的部分进行缓存。
在像这样的情形下, Django展示了一种位于django.core.cache模块中的简单、低层次的缓存
API。你可以使用这种低层次的缓存 API在缓存中以任何级别粒度进行对象储存。你可以对所有能够安全进行 pickle处理的
Python 对象进行缓存:字符串、字典和模型对象列表等等;查阅 Python文档可以了解到更多关于 pickling的信息。)
下面是如何导入这个 API :
>>> from django.core.cache import cache
基本的接口是set(key, value, timeout_seconds)和get(key):
>>> cache.set('my_key', 'hello, world!', 30) >>> cache.get('my_key') 'hello, world!'
timeout_seconds参数是可选的,并且默认为前面讲过的CACHE_BACKEND设置中的timeout参数.
如果对象在缓存中不存在,或者缓存后端是不可达的,cache.get()返回None:
# Wait 30 seconds for 'my_key' to expire... >>> cache.get('my_key') None >>> cache.get('some_unset_key') None
我们不建议在缓存中保存None常量,因为你将无法区分所保存的None变量及由返回值None所标识的缓存未中。
cache.get()接受一个缺省参数。其指定了当缓存中不存在该对象时所返回的值:
>>> cache.get('my_key', 'has expired')
'has expired'
要想一次获取多个缓存值,可以使用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.get_many(['a', 'b', 'c', 'd'])
{'a': 1, 'b': 2, 'c': 3}
最后,你可以用cache.delete()显式地删除关键字。这是在缓存中清除特定对象的简单途径。
>>> cache.delete('a')
cache.delete()没有返回值,不管给定的缓存关键字对应的值存在与否,它都将以同样方式工作。
上游缓存
目前为止,本章的焦点一直是对你自己的数据进行缓存。但还有一种与 Web开发相关的缓存:由上游高速缓存执行的缓冲。有一些系统甚至在请求到达站点之前就为用户进行页面缓存。
下面是上游缓存的几个例子:
§ 你的 ISP (互联网服务商)可能会对特定的页面进行缓存,因此如果你向http://www.infocool.net/请求一个页面,你的
ISP 可能无需直接访问 www.infocool.net就能将页面发送给你。而 www.infocool.net的维护者们却无从得知这种缓存,ISP位于
www.infocool.net和你的网页浏览器之间,透明底处理所有的缓存。
§ 你的 Django网站可能位于某个代理缓存之后,例如
Squid网页代理缓存 (http://www.squid-cache.org/),该缓存为提高性能而对页面进行缓存。在此情况下,每个请求将首先由代理服务器进行处理,然后仅在需要的情况下才被传递至你的应用程序。
§ 你的网页浏览器也对页面进行缓存。如果某网页送出了相应的头部,你的浏览器将在为对该网页的后续的访问请求使用本地缓存的拷贝,甚至不会再次联系该网页查看是否发生了变化。
上游缓存将会产生非常明显的效率提升,但也存在一定风险。许多网页的内容依据身份验证以及许多其他变量的情况发生变化,缓存系统仅盲目地根据 URL保存页面,可能会向这些页面的后续访问者暴露不正确或者敏感的数据。
举个例子,假定你在使用网页电邮系统,显然收件箱页面的内容取决于登录的是哪个用户。如果 ISP盲目地缓存了该站点,那么第一个用户通过该 ISP登录之后,他(或她)的用户收件箱页面将会缓存给后续的访问者。这一点也不好玩。
幸运的是, HTTP提供了解决该问题的方案。已有一些 HTTP头标用于指引上游缓存根据指定变量来区分缓存内容,并通知缓存机制不对特定页面进行缓存。我们将在本节后续部分将对这些头标进行阐述。
使用 Vary头标
Vary头标定义了缓存机制在构建其缓存键值时应当将哪个请求头标考虑在内。例如,如果网页的内容取决于用户的语言偏好,该页面被称为根据语言而不同。
缺省情况下,Django的缓存系统使用所请求的路径(比如:"/stories/2005/jun/23/bank_robbed/")来创建其缓存键。这意味着对该
URL的每个请求都将使用同一个已缓存版本,而不考虑 cookies或语言偏好之类的 user-agent差别。然而,如果该页面基于请求头标的区别(例如
cookies、语言或者 user-agent)产生不同内容,你就不得不使用
Vary头标来通知缓存机制:该页面的输出取决与这些东西。
要在 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-ControlHTTP指令都是有效的。以下是一个完整的清单:
§ 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-ControlHTTP指令的相关解释,
可以查阅http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9的规范文档。
注意
缓存中间件已经使用CACHE_MIDDLEWARE_SETTINGS设置设定了缓存头标max-age。如果你在cache_control修饰器中使用了自定义的max_age,该修饰器将会取得优先权,该头标的值将被正确地被合并。)
其他优化
Django 带有一些其它中间件可帮助您优化应用程序的性能:
§ django.middleware.http.ConditionalGetMiddleware は、最新のブラウザーの ETag および Last-Modified ヘッダーに基づく条件付き GET 応答のサポートを追加します。
§ django.middleware.gzip.GZipMiddleware は、最新のすべてのブラウザー向けに応答コンテンツを圧縮して、帯域幅と配信時間を節約します。
MIDDLEWARE_CLASSES の順序
キャッシュ ミドルウェアを使用する場合は、必ず MIDDLEWARE_CLASSES 設定の正しい場所に配置してください。これは、キャッシュ ミドルウェアは、さまざまなキャッシュ ストアの生成に使用されるヘッダーを認識する必要があるためです。
次のミドルウェアを含む、Vary ヘッダーにコンテンツを追加する可能性のあるすべてのミドルウェアの後に CacheMiddleware を配置します:
§ Cookie の SessionMiddleware を追加します
§ Accept-Encoding の GZipMiddleware を追加します
上記はDjango キャッシュ メカニズムの内容 さらに関連する内容については、PHP 中国語 Web サイト (www.php.cn) に注目してください。