Web服務器會執行各種計算--從數據庫查詢到模板渲染到商業邏輯--來創建你的站點的訪問者看到的頁面,從處理過度的角
度來看,這代價非常昂貴
對于大多數Web應用,過度不是大問題,大部分Web程序不是washingtonpost.com或者slashdot.org,它們只是簡單的小的
到中等大小的流量不過如此的站點,但是對于中等到高流量的站點,盡可能多的去除過度就很重要,所以緩存來了
緩存就是把一個昂貴計算的結果保存起來,于是你下次不需要再計算一次,這里是解釋它怎樣為動態網頁工作的偽代碼:
- 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://www.squid-cache.org)和基于瀏覽器的緩存,這些類型
的緩存你不直接控制,但是你可以提供關于你的站點哪部分應該被緩存和怎樣緩存的線索(通過HTTP頭部)給它們
設定緩存
緩存系統需要一些少量的設定工作,即你必需告訴它你的緩存數據在哪里--在數據庫,文件系統或者直接在內存中,這是影
響你的緩存性能的重要決定,是的,一些緩存類型要比其它的快,內存緩存通常比文件系統或數據庫緩存快,因為前者沒有
訪問文件系統或數據庫的過度
你的緩存選擇在你的settings文件的CACHE_BACKEND設置中,如果你使用緩存但沒有指定CACHE_BACKEND,Django將默認使用
simple:///,下面解釋了CACHE_BACKEND的所有可得到的值
Memcached
目前為止Django可得到的最快的最高效的緩存類型是基于內存的緩存框架Memcached,它起初開發來為LiveJournal.com處理
高負荷并隨后被Danga Interactive(http://www.danga.com)開源,它被Slashdot和Wikipedia等站點使用來減少
數據庫訪問和戲劇般的增加站點性能
Memcached可以在http://danga.com/memcached/免費得到,它作為后臺進程運行并分配一個指定數量的RAM--為
在緩存中添加,得到和刪除任意數據,所有的數據直接存儲在內存中,所以沒有數據庫和文件系統使用的過度
在安裝了Memcached本身之后,你將需要安裝Memcached Python綁定,它沒有直接和Django綁定,這些綁定在一個單獨的
Python模塊中,memcache.py,可以在http://www.djangoproject.com/thirdparty/python-memcached得到
設置CACHE_BACKEND為memcached://ip:port/來讓Django使用Memcached,這里的ip是Memcached后臺進程的IP地址,port則是
Memcached運行所在的端口
在這個例子中,Memcached運行在localhost(127.0.0.1)端口11211:
CACHE_BACKEND = 'memcached://127.0.0.1:11211/'
Memcached的一個極好的特性是它在多個服務器分享緩存的能力,這意味著你可以在多臺機器上運行Memcached進程,程序將
會把這組機器當作一個單獨的緩存,而不需要在每臺機器上復制緩存值,為了讓Django利用此特性,需要在CACHE_BACKEND
里包含所有的服務器地址并用分號分隔
這個例子中,緩存在運行在172.19.26.240和172.19.26.242的IP地址和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)的Memcach
ed實例間分享:
CACHE_BACKEND = 'memcached://172.19.26.240:11211;172.19.26.242:11212;172.19.26.244:11213/'
最后關于Memcached的是基于內存的緩存有一個重大的缺點,因為緩存數據只存儲在內存中,則如果服務器死機的話數據會丟
失,顯然內存不是為持久數據存儲準備的,Django沒有一個緩存后端是用來做持久存儲的,它們都是緩存方案,而不是存儲
但是我們在這里指出是因為基于內存的緩存特別的短暫
數據庫緩存
在你的數據庫創建緩存表并在表里指出Django的緩存系統來使用數據庫表作為緩存后端,首先運行這個命令創建緩存表:
python manage.py createcachetable [cache_table_name]
這里的[cache_table_name]是要創建的數據庫表名,名字可以是任何你想要的,只要它是合法的在你的數據庫中沒有被使用
這個命令在你的數據庫創建一個遵循Django的數據庫緩存系統期望形式的單獨的表
一旦你創建了數據庫表,設置你的CACHE_BACKEND設置為"db://tablename",這里的tablename是數據庫表的名字,在這個例
子中,緩存表名為my_cache_table:
CACHE_BACKEND = 'db://my_cache_table'
數據庫緩存后端使用你的settings文件指定的同一數據庫,你不能為你的緩存表使用不同的數據庫后端
文件系統緩存
使用"file://"緩存類型作為CACHE_BACKEND并指定存儲緩存數據的文件系統目錄來在文件系統存儲緩存條目
例如,使用下面的設置來在/var/tmp/django_cache存儲緩存數據:
CACHE_BACKEND = 'file:///var/tmp/django_cache'
注意例子中開頭有三個前斜線,前兩個是file://,第三個是目錄路徑的第一個字符,/var/tmp/django_cache,如果你使用
Windows,把盤符字母放在file://后面,像這樣:file://c:/foo/bar
目錄路徑應該是絕對路徑,即應該以你的文件系統的根開始,你在設置的結尾放置斜線與否無關緊要
確認該設置指向的目錄存在并且你的Web服務器運行的系統的用戶可以讀寫該目錄,繼續上面的例子,如果你的服務器以用戶
apache運行,確認/var/tmp/django_cache存在并且用戶apache可以讀寫/var/tmp/django_cache目錄
每個緩存值將被存儲為單獨的文件,其內容是Python的pickle模塊以序列化("pickled")形式保存的緩存數據,每個文件的
文件名是緩存鍵,并escape為安全的文件系統使用
本地存儲器緩存
如果你想要內存緩存的速度優勢但沒有能力運行Memcached,可以考慮使用本地存儲器緩存后端,該緩存是多線程和線程安全
的,但是由于其簡單的鎖和內存分配策略它沒有Memcached高效
設置CACHE_BACKEND為'locmem:///'來使用它,例如:
CACHE_BACKEND = 'locmem:///'
簡單緩存(開發用)
'simple:///'是一個簡單,單線程的內存緩存,它只在程序中保存緩存數據,這意味著它只能在開發或測試環境下使用,如:
CACHE_BACKEND = 'simple:///'
假緩存(開發用)
最后,Django帶有一個"假"緩存,它事實上不緩存--它只是實現了緩存接口但不做任何事情
如果你有一個產品站點,站點在不同的地方使用重型緩存但開發和測試環境中你不想使用緩存,則它是很有用的,這種情況
下,在settings文件中設置CACHE_BACKEND為'dummy:///'來做開發環境,這樣你的開發環境就不會使用緩存但你的產品環境
仍然會使用,例如:
CACHE_BACKEND = 'dummy:///'
CACHE_BACKEND參數
每個緩存后端都可能使用參數,它們在CACHE_BACKEND設置中以查詢字符串形式給出,合法的參數為:
1,timeout--緩存默認的超時限定,以秒為單位,默認為300秒(5分鐘)
2,max_entries--簡單后端,本地存儲器后端和數據庫后端緩存在舊值清楚前允許的最大的條目,默認為300
3,cull_percentage--當到達max_entries時選擇的條目的比率,準確的比率是1/cull_percentage,所以設置cull_percenta
ge=2則當max_entries到達時會選擇1/2的條目
cull_percentage=0表示當到達max_entries時條目緩存將被清除,這以更多的緩存缺失為代價讓選擇更快,默認值為3
這個例子中,timeout設置為60:
CACHE_BACKEND = "locmem:///?timeout=60"
這個例子中,timeout為30并且max_entries為400:
CACHE_BACKEND = "locmem:///?timeout=30&max_entries=400"
不合法的參數被靜靜的忽略,作為已知參數的非法值
整站緩存
一旦你指定了CACHE_BACKEND,使用緩存的最簡單的方式是緩存你的整個站點,這意味著每個沒有GET或POST參數的頁面第一
次請求時都會緩存一段特有的時間
把'django.middleware.cache.CacheMiddleware'添加到你的MIDDLEWARE_CLASSES設置中來激活整站緩存,例如:
- MIDDLEWARE_CLASSES = (
- 'django.middleware.cache.CacheMiddleware',
- 'django.middleware.common.CommonMiddleware',
- )
(MIDDLEWARE_CLASSES的順序有關系,參考下面的"MIDDLEWARE_CLASSES的順序")
然后,在你的Django settings文件中添加下列必需設置:
1,CACHE_MIDDLEWARE_SECONDS--每個頁面應該被緩存的秒數
2,CACHE_MIDDLEWARE_KEY_PREFIX--如果在同一Django安裝的多個站點分享緩存,設置它為站點名,或者其它唯一代表當前
Django實例的字符串來防止鍵沖突,如果你不在意則可以使用空字符串
緩存中間件緩存每個沒有GET或者POST參數的頁面,即如果用戶請求頁面并在查詢字符串里傳遞GET參數或者POST參數,中間
件將不會嘗試得到緩存版本的頁面,如果你打算使用整站緩存,設計你的程序時牢記這點,例如,不要使用擁有查詢字符串
的URLs,除非那些頁面可以不緩存
緩存中間件支持另一個設置,CACHE_MIDDLEWARE_ANONYMOUS_ONLY,如果你定義了這個設置,并且值設為True,則緩存中間件
將只緩存匿名請求,即那些沒有登錄的用戶的請求,這是對用戶特有的頁面禁止緩存的簡單和有效的方式,如Django的admin
界面,注意如果你使用CACHE_MIDDLEWARE_ANONYMOUS_ONLY,你應該確認你已經激活了AuthenticationMiddleware并且它在
你的MIDDLEWARE_CLASSES中的CacheMiddleware之前
最后,注意CacheMiddleware自動為每個HttpResponse設置一些頭部:
1,當一個新(沒緩存的)版本的頁面被請求時設置Last-Modified頭部為當前日期/時間
2,設置Expires頭部為當前日期/時間加上定義的CACHE_MIDDLEWARE_SECONDS
3,設置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)
如果你使用Python2.4或更高,你可以使用裝飾器語法,這個例子是相同的:
- from django.views.decorators.cache import cache_page
- @cache_page(60 * 15)
- def my_view(request, param):
- # ...
cache_page使用一個單獨的參數:緩存超時,以秒為單位,在上面的例子中,my_view()視圖的結果將被緩存15分鐘(注意我們
為了可讀性把它寫成了60 * 15,等于900,即15分鐘乘以每分鐘60秒)
類似于整站緩存,視圖緩存和URL有關,如果多個URLs指向同一視圖,則每個URL將被單獨緩存,繼續my_view例子,如果你的
URL配置像下面這樣:
- urlpatterns = ('',
- (r'^foo/(\d{1,2})/$', my_view),
- )
則對/foo/1/和/foo/23/的請求將像你期望的那樣被單獨緩存,但是一旦一個特別的URL(例如/foo/23/)被請求,則該URL后續
的請求將使用緩存
在URL配置指定視圖緩存
上面的例子硬編碼了視圖被緩存的事實,因為cache_page在適當的位置改變了my_view視圖,這種方式耦合了你的視圖和緩存
系統,在一些方面這是不理想的,例如,你可能想重用視圖方法到另一個很少緩存的站點,或者你可能想發布視圖給那些可
能想不用緩存來使用它們的人,這些問題的解決方案是在URL配置里指定視圖緩存而不是在視圖方法本身附近
這很容易做到,當你在URL配置里引用它的時候簡單的用cache_page包裝視圖方法,這里是上面舊的URL配置:
- urlpatterns = ('',
- (r'^foo/(\d{1,2})/$', my_view),
- )
這里是同樣的東西,但用cache_page包裝了my_view:
- from django.views.decorators.cache import cache_page
- urlpatterns = ('',
- (r'^foo/(\d{1,2})/$', cache_page(my_view, 60 * 15)),
- )
如果你使用這種方式,別忘了在你的URL配置里import cache_page
低級緩存API
有時候,緩存完整渲染的頁面不會讓你收獲很多,事實上,這有點不方便,例如你的站點包括一個結果依賴于一些昂貴查詢
的結果的視圖,而且結果在一段時間后會更改,這種情況下,使用整站緩存或視圖緩存策略提供的全頁面緩存就不是很理想
因為你不想緩存整個結果(既然有些數據頻繁更改的話),但是你仍然想緩存很少更改的結果
對于這種情況,Django暴露了一個簡單低級的緩存API,它位于django.core.cache,你可以使用任何粒度的低級緩存API來
在緩存中存儲對象,你可以緩存任何可以被安全"pickled"的Python對象--字符串,字典,模型對象列表等等(大部分通常的
Python對象都可以被pickled,參考Python文檔得到更多關于pickling的信息)
這里是怎樣import它:
- >>> 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()可以使用一個default參數,它指定了如果對象在緩存中不存在時的返回值:
- >>> 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開發相關:通過"上游"緩存來執行緩存,這些
緩存是在請求到達你的Web站點之前為用戶緩存頁面的系統
這里是一些上游緩存的例子:
1,你的ISP可能緩某些頁面,所以如果你請求example.com的一個頁面,你的ISP將不直接訪問example.com而發送給你那個
頁面,example.com的維護者不知道這個緩存,ISP位于example.com和你的Web瀏覽器之間透明的處理所有的緩存
2,你的Django網站可能在一個代理緩存后面,例如Squid(http://www.squid-cache.org),它為性能而緩存頁面
這種情況下,每個請求首先被代理處理,然后如果需要的話才被發送到你的程序
3,你的Web瀏覽器也緩存頁面,如果一個Web頁面發送適當的頭部,你的瀏覽器將為后面對該頁面的請求使用本地緩存拷貝
而不是再一次連接網頁來看它是否更改
上游緩存是很好的功效推進,但是它有一個危險,許多網頁的內容基于認證和一些其它變量而不同,并且完全基于URL來盲目
的保存頁面的緩存系統可能暴露不正確的或者敏感數據給后面訪問那些頁面的訪問者
例如,你操作一個Web e-mail系統,"收件箱"頁面的內容顯然依賴于登錄的用戶,如果ISP盲目的緩存你的站點,則第一個
通過ISP登錄的用戶將會使他的用戶專有的收件箱頁面緩存給后面訪問該站點的訪問者,這不cool
幸運的是,HTTP提供了該問題的解決方案,存在一些HTTP頭部來告知上游緩存根據指派的變量顯示不同的緩存內容,以及告
訴緩存機制不要緩存特殊的頁面
使用Vary頭部
這些頭部中的一個為Vary,它定義了當緩存機制構建它的緩存鍵時應該考慮哪個請求頭部,例如,如果一個網頁的內容依賴
于用戶的語言選擇,則這個頁面稱為"根據語言而不同"
Django的緩存系統默認使用請求路徑來創建它的緩存鍵,例如"/stories/2005/jun/23/bank_robbed/",這意味著對該URL的
每個請求將使用同樣的緩存版本,不管user-agent是否不同,如cookies或者語言選擇等等,盡管如此,如果頁面根據請求
頭部的一些不同來輸出不同的內容--如cookie,語言,或user-agent--你將需要使用Vary頭部來告訴緩存機制頁面輸出依賴
于那些東西,像這樣使用方便的vary_on_headers視圖裝飾器來在Django中做這個:
- 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自己的緩存中間件)將對每個唯一的user-agent緩存單獨版本的頁面
使用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的結合得到它自己的緩存值,例如,使用user-agent為Mozilla
和cookie值foo=bar的請求將被認為和user-agent為Mozilla和cookie值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頭部,例如:
- 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程序需要一種告訴緩存那些數據是私有和那些數據是公眾的方式
解決方案是指出一個頁面的緩存應該為"私有",使用cache_control視圖裝飾器來在Django中做這件事,例如:
- from django.views.decorators.cache import cache_control
- @cache_control(private=True)
- def my_view(request):
- # ...
這個裝飾器在幕后處理發送適當的HTTP頭部
有一些其它控制緩存參數的方式,例如,HTTP允許程序做下面的事情:
1,定義一個頁面緩存的最大時間
2,指定一個緩存是否應該一直檢查新的版本,只有在內容沒有更改時發送緩存(一些緩存可能即使服務器頁面更改了也發送
緩存內容--簡單的因為緩存拷貝沒有過期)
在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 HTTP指示在cache_control()中是合法的,這里是完整的列表:
1,public=True
2,private=True
3,no_cache=True
4,no_transform=True
5,must_revalidate=True
6,proxy_revalidate=True
7,max_age=num_seconds
8,s_maxage=num_seconds
參考規范http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9來得到Cache-Control HTTP指示
的解釋
(注意,緩存中間件已經通過CACHE_MIDDLEWARE_SETTINGS設置來設置了緩存頭部的max-age,如果你在cache_control裝飾器
中使用自定義的max_age,裝飾器將優先采用,而頭部的值會被正確的合并)
其它優化
Django一些其它可以幫你優化你的apps性能的中間件:
1,django.middleware.http.ConditionalGetMiddleware添加對現代瀏覽器的基于ETag和Last-Modified頭部的有條件的GET
應答的支持
2,django.middleware.gzip.GZipMiddleware為所有現代瀏覽器壓縮應答來節省帶寬和傳輸時間
MIDDLEWARE_CLASSES的順序
如果你使用CacheMiddleware,把它放在MIDDLEWARE_CLASSES設置的正確位置很重要,因為緩存中間件需要知道改變緩存存儲
的頭部,把CacheMiddleware放在任何可能添加東西到Vary頭部的中間件后面,包括:
1,SessionMiddleware,它添加Cookie
2,GZipMiddleware,它添加Accept-Encoding
安徽新華電腦學校專業職業規劃師為你提供更多幫助【在線咨詢】