動態(tài)網(wǎng)站的問題就在于它是動態(tài)的。 也就是說每次用戶訪問一個頁面,服務(wù)器要執(zhí)行數(shù)據(jù)庫查詢,啟動模板,執(zhí)行業(yè)務(wù)邏輯以及最終生成一個你所看到的網(wǎng)頁,這一切都是動態(tài)即時生成的。 從處理器資源的角度來看,這是比較昂貴的。
對于大多數(shù)網(wǎng)絡(luò)應(yīng)用來說,過載并不是大問題。 因?yàn)榇蠖鄶?shù)網(wǎng)絡(luò)應(yīng)用并不是washingtonpost.com或Slashdot;它們通常是很小很簡單,或者是中等規(guī)模的站點(diǎn),只有很少的流量。 但是對于中等至大規(guī)模流量的站點(diǎn)來說,盡可能地解決過載問題是非常必要的。
這就需要用到緩存了。
緩存的目的是為了避免重復(fù)計(jì)算,特別是對一些比較耗時間、資源的計(jì)算。 下面的偽代碼演示了如何對動態(tài)頁面的結(jié)果進(jìn)行緩存。
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提供了一個穩(wěn)定的緩存系統(tǒng)讓你緩存動態(tài)頁面的結(jié)果,這樣在接下來有相同的請求就可以直接使用緩存中的數(shù)據(jù),避免不必要的重復(fù)計(jì)算。 另外Django還提供了不同粒度數(shù)據(jù)的緩存,例如: 你可以緩存整個頁面,也可以緩存某個部分,甚至緩存整個網(wǎng)站。
Django也和”上游”緩存工作的很好,例如Squid(http://www.squid-cache.org)和基于瀏覽器的緩存。 這些類型的緩存你不直接控制,但是你可以提供關(guān)于你的站點(diǎn)哪部分應(yīng)該被緩存和怎樣緩存的線索(通過HTTP頭部)給它們
緩存系統(tǒng)需要一些少量的設(shè)定工作。 也就是說,你必須告訴它緩存的數(shù)據(jù)應(yīng)該放在哪里,在數(shù)據(jù)庫中,在文件系統(tǒng),或直接在內(nèi)存中。 這是一個重要的決定,影響您的高速緩存的性能,是的,有些類型的緩存比其它類型快。
緩存設(shè)置在settings文件的 CACHE_BACKEND中。 這里是一個CACHE_BACKEND所有可用值的解釋。
Memcached是迄今為止可用于Django的最快,最有效的緩存類型,Memcached是完全基于內(nèi)存的緩存框架,最初開發(fā)它是用以處理高負(fù)荷的LiveJournal.com隨后由Danga Interactive公司開源。 它被用于一些站點(diǎn),例如Facebook和維基百科網(wǎng)站,以減少數(shù)據(jù)庫訪問,并大幅提高站點(diǎn)的性能。
Memcached是免費(fèi)的(http://danga.com/memcached)。它作為一個守護(hù)進(jìn)程運(yùn)行,并分配了特定數(shù)量的內(nèi)存。 它只是提供了添加,檢索和刪除緩存中的任意數(shù)據(jù)的高速接口。 所有數(shù)據(jù)都直接存儲在內(nèi)存中,所以沒有對使用的數(shù)據(jù)庫或文件系統(tǒng)的開銷。
在安裝了Memcached本身之后,你將需要安裝Memcached Python綁定,它沒有直接和Django綁定。 這兩個可用版本。 選擇和安裝以下模塊之一:
最快的可用選項(xiàng)是一個模塊,稱為cmemcache,在http://gijsbert.org/cmemcache。
若要使用Memcached的Django,設(shè)置CACHE_BACKEND到memcached:/ / IP:port/,其中IP是Memcached的守護(hù)進(jìn)程的IP地址,port是Memcached運(yùn)行的端口。
在這個例子中,Memcached運(yùn)行在本地主機(jī) (127.0.0.1)上,端口為11211:
CACHE_BACKEND = 'memcached://127.0.0.1:11211/'
Memcached的一個極好的特性是它在多個服務(wù)器間分享緩存的能力。 這意味著您可以在多臺機(jī)器上運(yùn)行Memcached的守護(hù)進(jìn)程,該程序會把這些機(jī)器當(dāng)成一個單一緩存,而無需重復(fù)每臺機(jī)器上的緩存值。 要充分利用此功能,請?jiān)贑ACHE_BACKEND里引入所有服務(wù)器的地址,用分號分隔。
這個例子中,緩存在運(yùn)行在IP地址為172.19.26.240和172.19.26.242,端口號為11211的Memcached實(shí)例間分享:
CACHE_BACKEND = 'memcached://172.19.26.240:11211;172.19.26.242:11211/'
這個例子中,緩存在運(yùn)行在172.19.26.240(端口11211),172.19.26.242(端口11212),172.19.26.244(端口11213)的Memcached實(shí)例間分享:
CACHE_BACKEND = 'memcached://172.19.26.240:11211;172.19.26.242:11212;172.19.26.244:11213/'
最后有關(guān)Memcached的一點(diǎn)是,基于內(nèi)存的緩存有一個重大的缺點(diǎn)。 由于緩存的數(shù)據(jù)存儲在內(nèi)存中,所以如果您的服務(wù)器崩潰,數(shù)據(jù)將會消失。 顯然,內(nèi)存不是用來持久化數(shù)據(jù)的,因此不要把基于內(nèi)存的緩存作為您唯一的存儲數(shù)據(jù)緩存。 毫無疑問,在Django的緩存后端不應(yīng)該用于持久化,它們本來就被設(shè)計(jì)成緩存的解決方案。但我們?nèi)匀恢赋龃它c(diǎn),這里是因?yàn)榛趦?nèi)存的緩存是暫時的。
為了使用數(shù)據(jù)庫表作為緩存后端,首先在數(shù)據(jù)庫中運(yùn)行這個命令以創(chuàng)建緩存表:
python manage.py createcachetable [cache_table_name]
這里的[cache_table_name]是要創(chuàng)建的數(shù)據(jù)庫表名。 (這個名字隨你的便,只要它是一個有效的表名,而且不是已經(jīng)在您的數(shù)據(jù)庫中使用的表名。)這個命令以Django的數(shù)據(jù)庫緩存系統(tǒng)所期望的格式創(chuàng)建一個表。
一旦你創(chuàng)建了數(shù)據(jù)庫表,把你的CACHE_BACKEND設(shè)置為”db://tablename”,這里的tablename是數(shù)據(jù)庫表的名字,在這個例子中,緩存表名為my_cache_table: 在這個例子中,高速緩存表的名字是my_cache_table:
CACHE_BACKEND = 'db://my_cache_table'
數(shù)據(jù)庫緩存后端使用你的settings文件指定的同一數(shù)據(jù)庫。 你不能為你的緩存表使用不同的數(shù)據(jù)庫后端.
如果你已經(jīng)有了一個快速,良好的索引數(shù)據(jù)庫服務(wù)器,那么數(shù)據(jù)庫緩存的效果最明顯。
要把緩存項(xiàng)目放在文件系統(tǒng)上,請為CACHE_BACKEND使用”file://“的緩存類型。例如,要把緩存數(shù)據(jù)存儲在/var/tmp/django_cache上,請使用此設(shè)置:
CACHE_BACKEND = 'file:///var/tmp/django_cache'
注意例子中開頭有三個斜線。 頭兩項(xiàng)是file://,第三個是第一個字符的目錄路徑,/var/tmp/django_cache。如果你使用的是Windows,在file://之后加上文件的驅(qū)動器號:
file://c:/foo/bar
目錄路徑應(yīng)該是絕對路徑,即應(yīng)該以你的文件系統(tǒng)的根開始。 在設(shè)置的結(jié)尾放置斜線與否無關(guān)緊要。
確認(rèn)該設(shè)置指向的目錄存在并且你的Web服務(wù)器運(yùn)行的系統(tǒng)的用戶可以讀寫該目錄。 繼續(xù)上面的例子,如果你的服務(wù)器以用戶apache運(yùn)行,確認(rèn)/var/tmp/django_cache存在并且用戶apache可以讀寫/var/tmp/django_cache目錄。
每個緩存值將被存儲為單獨(dú)的文件,其內(nèi)容是Python的pickle模塊以序列化(“pickled”)形式保存的緩存數(shù)據(jù)。 每個文件的名稱是緩存鍵,以規(guī)避開安全文件系統(tǒng)的使用。
如果你想利用內(nèi)存緩存的速度優(yōu)勢,但又不能使用Memcached,可以考慮使用本地存儲器緩存后端。 此緩存的多進(jìn)程和線程安全。 設(shè)置 CACHE_BACKEND 為 locmem:/// 來使用它,例如:
CACHE_BACKEND = 'locmem:///'
請注意,每個進(jìn)程都有自己私有的緩存實(shí)例,這意味著跨進(jìn)程緩存是不可能的。 這顯然也意味著本地內(nèi)存緩存效率并不是特別高,所以對產(chǎn)品環(huán)境來說它可能不是一個好選擇。 對開發(fā)來說還不錯。
最后,Django提供了一個假緩存(只是實(shí)現(xiàn)了緩存接口,實(shí)際上什么都不做)。
假如你有一個產(chǎn)品站點(diǎn),在許多地方使用高度緩存,但在開發(fā)/測試環(huán)境中,你不想緩存,也不想改變代碼,這就非常有用了。 要激活虛擬緩存,就像這樣設(shè)置CACHE_BACKEND:
CACHE_BACKEND = 'dummy:///'
盡管Django包含對許多緩存后端的支持,在某些情況下,你仍然想使用自定義緩存后端。 要讓Django使用外部緩存后端,需要使用一個Python import路徑作為的CACHE_BACKEND URI的(第一個冒號前的部分),像這樣:
CACHE_BACKEND = 'path.to.backend://'
如果您構(gòu)建自己的后端,你可以參考標(biāo)準(zhǔn)緩存后端的實(shí)現(xiàn)。 源代碼在Django的代碼目錄的django/core/cache/backends/下。
注意 如果沒有一個真正令人信服的理由,比如主機(jī)不支持,你就應(yīng)該堅(jiān)持使用Django包含的緩存后端。 它們經(jīng)過大量測試,并且易于使用。
每個緩存后端都可能使用參數(shù)。 它們在CACHE_BACKEND設(shè)置中以查詢字符串形式給出。 有效參數(shù)如下:
timeout:用于緩存的過期時間,以秒為單位。 這個參數(shù)默認(rèn)被設(shè)置為300秒(五分鐘)。
max_entries:對于內(nèi)存,文件系統(tǒng)和數(shù)據(jù)庫后端,高速緩存允許的最大條目數(shù),超出這個數(shù)則舊值將被刪除。 這個參數(shù)默認(rèn)是300。
cull_percentage :當(dāng)達(dá)到 max_entries 的時候,被刪除的條目比率。 實(shí)際的比率是 1/cull_percentage ,所以設(shè)置cull_frequency=2就是在達(dá)到 max_entries 的時候去除一半數(shù)量的緩存。
把 cull_frequency 的值設(shè)置為 0 意味著當(dāng)達(dá)到 max_entries 時,緩存將被清空。 這將以很多緩存丟失為代價,大大提高接受訪問的速度。
在這個例子中, timeout 被設(shè)成 60
CACHE_BACKEND = "memcached://127.0.0.1:11211/?timeout=60"
而在這個例子中, timeout 設(shè)為 30 而 max_entries 為 400 :
CACHE_BACKEND = "locmem:///?timeout=30&max_entries=400"
其中,非法的參數(shù)與非法的參數(shù)值都將被忽略。
一旦高速緩存設(shè)置,最簡單的方法是使用緩存緩存整個網(wǎng)站。 您 需要添加’django.middleware.cache.UpdateCacheMiddleware’和 ‘django.middleware.cache.FetchFromCacheMiddleware’到您的MIDDLEWARE_CLASSES設(shè)置中,在這個例子中是:
MIDDLEWARE_CLASSES = (
'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
)
注意:
不,這里并沒有排版錯誤: 修改的中間件,必須放在列表的開始位置,而fectch中間件,必須放在最后。 細(xì)節(jié)有點(diǎn)費(fèi)解,如果您想了解完整內(nèi)幕請參看下面的MIDDLEWARE_CLASSES順序。
然后,在你的Django settings文件里加入下面所需的設(shè)置:
CACHE_MIDDLEWARE_SECONDS :每個頁面應(yīng)該被緩存的秒數(shù)。
緩存中間件緩存每個沒有GET或者POST參數(shù)的頁面。 或者,如果CACHE_MIDDLEWARE_ANONYMOUS_ONLY設(shè)置為True,只有匿名請求(即不是由登錄的用戶)將被緩存。 如果想取消用戶相關(guān)頁面(user-specific pages)的緩存,例如Djangos 的管理界面,這是一種既簡單又有效的方法。 CACHE_MIDDLEWARE_ANONYMOUS_ONLY,你應(yīng)該確保你已經(jīng)啟動AuthenticationMiddleware。
此外,緩存中間件為每個HttpResponse自動設(shè)置了幾個頭部信息:
當(dāng)一個新(沒緩存的)版本的頁面被請求時設(shè)置Last-Modified頭部為當(dāng)前日期/時間。
設(shè)置Expires頭部為當(dāng)前日期/時間加上定義的CACHE_MIDDLEWARE_SECONDS。
參閱更多的中間件第17章。
如果視圖設(shè)置自己的緩存到期時間(即 它有一個最大年齡在頭部信息的Cache-Control中),那么頁面將緩存直到過期,而不是CACHE_MIDDLEWARE_SECONDS。使用django.views.decorators.cache裝飾器,您可以輕松地設(shè)置視圖的到期時間(使用cache_control裝飾器)或禁用緩存視圖(使用never_cache裝飾器)。 請參閱下面的”使用其他頭部信息“小節(jié)以了解裝飾器的更多信息。
更加顆粒級的緩存框架使用方法是對單個視圖的輸出進(jìn)行緩存。 django.views.decorators.cache定義了一個自動緩存視圖響應(yīng)的cache_page裝飾器。 他是很容易使用的:
from django.views.decorators.cache import cache_page
def my_view(request):
# ...
my_view = cache_page(my_view, 60 * 15)
也可以使用Python2.4的裝飾器語法:
@cache_page(60 * 15)
def my_view(request):
# ...
cache_page 只接受一個參數(shù): 以秒計(jì)的緩存超時時間。 在前例中, “my_view()” 視圖的結(jié)果將被緩存 15 分鐘。 (注意: 為了提高可讀性,該參數(shù)被書寫為 60 15 。 60 15 將被計(jì)算為 900 ,也就是說15 分鐘乘以每分鐘 60 秒。)
和站點(diǎn)緩存一樣,視圖緩存與 URL 無關(guān)。 如果多個 URL 指向同一視圖,每個視圖將會分別緩存。 繼續(xù)my_view 范例,如果 URLconf 如下所示:
urlpatterns = ('',
(r'^foo/(\d{1,2})/$', my_view),
)
那么正如你所期待的那樣,發(fā)送到 /foo/1/ 和 /foo/23/ 的請求將會分別緩存。 但一旦發(fā)出了特定的請求(如: /foo/23/ ),之后再度發(fā)出的指向該 URL 的請求將使用緩存。
前一節(jié)中的范例將視圖硬編碼為使用緩存,因?yàn)?nbsp;cache_page 在適當(dāng)?shù)奈恢脤?nbsp;my_view 函數(shù)進(jìn)行了轉(zhuǎn)換。 該方法將視圖與緩存系統(tǒng)進(jìn)行了耦合,從幾個方面來說并不理想。 例如,你可能想在某個無緩存的站點(diǎn)中重用該視圖函數(shù),或者你可能想將該視圖發(fā)布給那些不想通過緩存使用它們的人。 解決這些問題的方法是在 URLconf 中指定視圖緩存,而不是緊挨著這些視圖函數(shù)本身來指定。
完成這項(xiàng)工作非常簡單: 在 URLconf 中用到這些視圖函數(shù)的時候簡單地包裹一個 cache_page 。以下是剛才用到過的 URLconf : 這是之前的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 中導(dǎo)入 cache_page。
你同樣可以使用cache標(biāo)簽來緩存模板片段。 在模板的頂端附近加入{% load cache %}以通知模板存取緩存標(biāo)簽。
模板標(biāo)簽{% cache %}在給定的時間內(nèi)緩存了塊的內(nèi)容。 它至少需要兩個參數(shù): 緩存超時時間(以秒計(jì))和指定緩存片段的名稱。 示例:
{% load cache %}
{% cache 500 sidebar %}
.. sidebar ..
{% endcache %}
有時你可能想緩存基于片段的動態(tài)內(nèi)容的多份拷貝。 比如,你想為上一個例子的每個用戶分別緩存?zhèn)冗厵凇?這樣只需要給{% cache %}傳遞額外的參數(shù)以標(biāo)識緩存片段。
{% load cache %}
{% cache 500 sidebar request.user.username %}
.. sidebar for logged in user ..
{% endcache %}
傳遞不止一個參數(shù)也是可行的。 簡單地把參數(shù)傳給{% cache %}。
緩存超時時間可以作為模板變量,只要它可以解析為整數(shù)值。 例如,如果模板變量my_timeout值為600,那么以下兩個例子是等價的。
{% cache 600 sidebar %} ... {% endcache %}
{% cache my_timeout sidebar %} ... {% endcache %}
這個特性在避免模板重復(fù)方面非常有用。 可以把超時時間保存在變量里,然后在別的地方復(fù)用。
有些時候,對整個經(jīng)解析的頁面進(jìn)行緩存并不會給你帶來太多好處,事實(shí)上可能會過猶不及。
比如說,也許你的站點(diǎn)所包含的一個視圖依賴幾個費(fèi)時的查詢,每隔一段時間結(jié)果就會發(fā)生變化。 在這種情況下,使用站點(diǎn)級緩存或者視圖級緩存策略所提供的整頁緩存并不是最理想的,因?yàn)槟憧赡懿粫雽φ麄€結(jié)果進(jìn)行緩存(因?yàn)橐恍?shù)據(jù)經(jīng)常變化),但你仍然會想對很少變化的部分進(jìn)行緩存。
針對這樣的情況,Django提供了簡單低級的緩存API。 你可以通過這個API,以任何你需要的粒度來緩存對象。 你可以對所有能夠安全進(jìn)行 pickle 處理的 Python 對象進(jìn)行緩存: 字符串、字典和模型對象列表等等。 (查閱 Python 文檔可以了解到更多關(guān)于 pickling 的信息。)
緩存模塊django.core.cache擁有一個自動依據(jù)CACHE_BACKEND設(shè)置創(chuàng)建的django.core.cache對象。
>>> 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 參數(shù)是可選的, 并且默認(rèn)為前面講過的 CACHE_BACKEND 設(shè)置中的 timeout 參數(shù).
如果緩存中不存在該對象,那么cache.get()會返回None。
# Wait 30 seconds for 'my_key' to expire...
>>> cache.get('my_key')
None
我們不建議在緩存中保存 None 常量,因?yàn)槟銓o法區(qū)分你保存的 None 變量及由返回值 None 所標(biāo)識的緩存未命中。
cache.get() 接受一個 缺省 參數(shù)。 它指定了當(dāng)緩存中不存在該對象時所返回的值:
>>> cache.get('my_key', 'has expired')
'has expired'
使用add()方法來新增一個原來沒有的鍵值。 它接受的參數(shù)和set()一樣,但是并不去嘗試更新已經(jīng)存在的鍵值。
>>> cache.set('add_key', 'Initial value')
>>> cache.add('add_key', 'New value')
>>> cache.get('add_key')
'Initial value'
如果想確定add()是否成功添加了緩存值,你應(yīng)該測試返回值。 成功返回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}
最后,你可以用 cache.delete() 顯式地刪除關(guān)鍵字。
>>> cache.delete('a')
也可以使用incr()或者decr()來增加或者減少已經(jīng)存在的鍵值。 默認(rèn)情況下,增加或減少的值是1??梢杂脜?shù)來制定其他值。 如果嘗試增減不存在的鍵值會拋出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),增減操作才是原子的。 然而,如果后端并不原生支持增減操作,也可以通過取值/更新兩步操作來實(shí)現(xiàn)。
目前為止,本章的焦點(diǎn)一直是對你 自己的 數(shù)據(jù)進(jìn)行緩存。 但還有一種與 Web 開發(fā)相關(guān)的緩存: 上游緩存。 有一些系統(tǒng)甚至在請求到達(dá)站點(diǎn)之前就為用戶進(jìn)行頁面緩存。
下面是上游緩存的幾個例子:
你的 ISP (互聯(lián)網(wǎng)服務(wù)商)可能會對特定的頁面進(jìn)行緩存,因此如果你向 http://example.com/ 請求一個頁面,你的 ISP 可能無需直接訪問 example.com 就能將頁面發(fā)送給你。 而 example.com 的維護(hù)者們卻無從得知這種緩存,ISP 位于 example.com 和你的網(wǎng)頁瀏覽器之間,透明地處理所有的緩存。
你的 Django 網(wǎng)站可能位于某個 代理緩存 之后,例如 Squid 網(wǎng)頁代理緩存 (http://www.squid-cache.org/),該緩存為提高性能而對頁面進(jìn)行緩存。 在此情況下 ,每個請求將首先由代理服務(wù)器進(jìn)行處理,然后僅在需要的情況下才被傳遞至你的應(yīng)用程序。
上游緩存將會產(chǎn)生非常明顯的效率提升,但也存在一定風(fēng)險。 許多網(wǎng)頁的內(nèi)容依據(jù)身份驗(yàn)證以及許多其他變量的情況發(fā)生變化,緩存系統(tǒng)僅盲目地根據(jù) URL 保存頁面,可能會向這些頁面的后續(xù)訪問者暴露不正確或者敏感的數(shù)據(jù)。
舉個例子,假定你在使用網(wǎng)頁電郵系統(tǒng),顯然收件箱頁面的內(nèi)容取決于登錄的是哪個用戶。 如果 ISP 盲目地緩存了該站點(diǎn),那么第一個用戶通過該 ISP 登錄之后,他(或她)的用戶收件箱頁面將會緩存給后續(xù)的訪問者。 這一點(diǎn)也不好玩。
幸運(yùn)的是, HTTP 提供了解決該問題的方案。 已有一些 HTTP 頭標(biāo)用于指引上游緩存根據(jù)指定變量來區(qū)分緩存內(nèi)容,并通知緩存機(jī)制不對特定頁面進(jìn)行緩存。 我們將在本節(jié)后續(xù)部分將對這些頭標(biāo)進(jìn)行闡述。
Vary 頭部定義了緩存機(jī)制在構(gòu)建其緩存鍵值時應(yīng)當(dāng)將哪個請求頭標(biāo)考慮在內(nèi)。 例如,如果網(wǎng)頁的內(nèi)容取決于用戶的語言偏好,該頁面被稱為根據(jù)語言而不同。
缺省情況下,Django 的緩存系統(tǒng)使用所請求的路徑(比如:"/stories/2005/jun/23/bank_robbed/" )來創(chuàng)建其緩存鍵。這意味著每次請求都會使用同樣的緩存版本,不考慮才客戶端cookie和語言配置的不同。 除非你使用Vary頭部通知緩存機(jī)制頁面輸出要依據(jù)請求頭里的cookie,語言等的設(shè)置而不同。
要在 Django 完成這項(xiàng)工作,可使用便利的 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):
# ...
在這種情況下,緩存機(jī)制(如 Django 自己的緩存中間件)將會為每一個單獨(dú)的用戶瀏覽器緩存一個獨(dú)立的頁面版本。
使用 vary_on_headers 裝飾器而不是手動設(shè)置 Vary 頭部(使用像 response['Vary'] = 'user-agent' 之類的代碼)的好處是修飾器在(可能已經(jīng)存在的) Vary 之上進(jìn)行 添加 ,而不是從零開始設(shè)置,且可能覆蓋該處已經(jīng)存在的設(shè)置。
你可以向 vary_on_headers() 傳入多個頭標(biāo):
@vary_on_headers('User-Agent', 'Cookie')
def my_view(request):
# ...
該段代碼通知上游緩存對 兩者 都進(jìn)行不同操作,也就是說 user-agent 和 cookie 的每種組合都應(yīng)獲取自己的緩存值。 舉例來說,使用 Mozilla 作為 user-agent 而 foo=bar 作為 cookie 值的請求應(yīng)該和使用 Mozilla 作為 user-agent 而 foo=ham 的請求應(yīng)該被視為不同請求。
由于根據(jù) cookie 而區(qū)分對待是很常見的情況,因此有 vary_on_cookie 裝飾器。 以下兩個視圖是等效的:
@vary_on_cookie
def my_view(request):
# ...
@vary_on_headers('Cookie')
def my_view(request):
# ...
傳入 vary_on_headers 頭標(biāo)是大小寫不敏感的; "User-Agent" 與 "user-agent" 完全相同。
你也可以直接使用幫助函數(shù):django.utils.cache.patch_vary_headers。 該函數(shù)設(shè)置或增加 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 實(shí)例為第一個參數(shù),以一個大小寫不敏感的頭標(biāo)名稱列表或元組為第二個參數(shù)。
關(guān)于緩存剩下的問題是數(shù)據(jù)的隱私性以及在級聯(lián)緩存中數(shù)據(jù)應(yīng)該在何處儲存的問題。
通常用戶將會面對兩種緩存: 他或她自己的瀏覽器緩存(私有緩存)以及他或她的提供者緩存(公共緩存)。 公共緩存由多個用戶使用,而受其他某人的控制。 這就產(chǎn)生了你不想遇到的敏感數(shù)據(jù)的問題,比如說你的銀行賬號被存儲在公眾緩存中。 因此,Web 應(yīng)用程序需要以某種方式告訴緩存那些數(shù)據(jù)是私有的,哪些是公共的。
解決方案是標(biāo)示出某個頁面緩存應(yīng)當(dāng)是私有的。 要在 Django 中完成此項(xiàng)工作,可使用 cache_control 視圖修飾器: 例如:
from django.views.decorators.cache import cache_control
@cache_control(private=True)
def my_view(request):
# ...
該修飾器負(fù)責(zé)在后臺發(fā)送相應(yīng)的 HTTP 頭部。
還有一些其他方法可以控制緩存參數(shù)。 例如, HTTP 允許應(yīng)用程序執(zhí)行如下操作:
定義頁面可以被緩存的最大時間。
在 Django 中,可使用 cache_control 視圖修飾器指定這些緩存參數(shù)。 在本例中, cache_control 告訴緩存對每次訪問都重新驗(yàn)證緩存并在最長 3600 秒內(nèi)保存所緩存版本:
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
緩存中間件已經(jīng)使用 CACHE_MIDDLEWARE_SETTINGS 設(shè)置設(shè)定了緩存頭部 max-age 。 如果你在cache_control修飾器中使用了自定義的max_age,該修飾器將會取得優(yōu)先權(quán),該頭部的值將被正確地被合并。
如果你想用頭部完全禁掉緩存,django.views.decorators.cache.never_cache裝飾器可以添加確保響應(yīng)不被緩存的頭部信息。 例如:
from django.views.decorators.cache import never_cache
@never_cache
def myview(request):
# ...
Django 帶有一些其它中間件可幫助您優(yōu)化應(yīng)用程序的性能:
django.middleware.http.ConditionalGetMiddleware 為現(xiàn)代瀏覽器增加了有條件的,基于 ETag 和Last-Modified 頭標(biāo)的GET響應(yīng)的相關(guān)支持。
如果使用緩存中間件,注意在MIDDLEWARE_CLASSES設(shè)置中正確配置。 因?yàn)榫彺嬷虚g件需要知道哪些頭部信息由哪些緩存區(qū)來區(qū)分。 中間件總是盡可能得想Vary響應(yīng)頭中添加信息。
UpdateCacheMiddleware在相應(yīng)階段運(yùn)行。因?yàn)橹虚g件是以相反順序運(yùn)行的,所有列表頂部的中間件反而_last_在相應(yīng)階段的最后運(yùn)行。 所有,你需要確保UpdateCacheMiddleware排在任何可能往_Vary_頭部添加信息的中間件之前。 下面的中間件模塊就是這樣的:
添加 Cookie 的 SessionMiddleware
添加 Accept-Encoding 的 GZipMiddleware
另一方面,F(xiàn)etchFromCacheMiddleware在請求階段運(yùn)行,這時中間件循序執(zhí)行,所以列表頂端的項(xiàng)目會_首先_執(zhí)行。 FetchFromCacheMiddleware也需要在會修改Vary頭部的中間件之后運(yùn)行,所以FetchFromCacheMiddleware必須放在它們后面。
Django捆綁了一系列可選的方便特性。 我們已經(jīng)介紹了一些: admin站點(diǎn)(第六章)和session/user框架(第十四章)。 下一章中,我們將講述Django中其他的子框架。