參考:http://blog.csdn.net/youmypig/article/details/8161809 html
2008年4月28日linux
大約一個星期前,安德魯 和 我 啓動 一個新的 Django 打造的網站,站名叫 Hey!Wall 。這是一個按照社交網絡中的「牆」的概念創建的社交網站,它爲各種朋友提供了一個留言及分享照片、視頻和連接的空間。數據庫
咱們想對其進行性能評估,並進行一些服務器配置和代碼修改來決定採起何種步驟進行改進。咱們使用 httperf
進行了測試,並經過優化將其性能提升了整整一倍。apache
服務器一是一臺 Slicehost 提供的 Xen VPS ,配有 256MB 內存,運行的是 Debian Etch 系統。部署在美國中西部。django
爲了測試,採用了一臺位於英國的 Xtraordinary Hosting 提供的 Xen VPS,做爲客戶端。一般咱們使用的 ADSL 訪問互聯網絡,但這讓咱們很難向服務器發起足夠多的訪問請求。使用鏈接良好的 VPS 做爲客戶端使咱們能夠真正地對服務器加以考驗。後端
很難確切地描述服務器的規格。該 VPS 配有 256MB 內存,與數個相似的 VPS 同居一臺主機(大概是一臺 裝有 16GB 內存的 Quad Core 服務器)之上。假定裝滿了 256MB 切片的話,物理服務器上最多裝有 64 臺 VPS 。若是四個處理器都是 2.4GHz,那麼共 9.6 GHz ,除以 64 獲得最少 150MHz 的 CPU 。緩存
在 Xen VPS 上,無需競爭你就能夠得到穩定的內存和 CPU 分配,但一般 主機上任何空閒的 CPU 都將獲得使用。若是在同一機器上的其它 VPS 處於空閒狀態,你的 VPS 將可以使用更多的 CPU 。這也許意味着在測試期間使用了更多的 CPU ,即某些測試可能比其它的使用了更多的 CPU 資源。服務器
現有各式各樣的網絡性能測試工具,主要包括 ab (來自 Apache), Flood 和 httperf。咱們使用 httperf 並無任何特別理由。cookie
httperf 命令看起來以下所示:網絡
httperf --hog --server=example.com --uri=/ --timeout=10 --num-conns=200 --rate=5
在該例中,咱們向 http://example.com/
發起了 200 次訪問請求,每秒最多 5 次。
某些工具支持進程,能夠模仿用戶對網站提交任務。咱們使用了一種簡單的「暴力」測試來了解該站點每秒可以處理多少請求。
該基本方法是發起必定數量的請求,可以判斷服務器如何反應:狀態 200 爲成功,狀態 500 爲失敗。提升頻率(每秒製造的請求數量)而後再試一遍。若是開始返回大量的 500 ,則已經達到極限。
另外一個方面是要掌握服務器在內存和 CPU 使用方面的狀況。要跟蹤這一狀況,咱們運行 top
並將輸出記錄爲日誌文件以供稍後查閱。該 top 命令以下所示:
top -b -d 3 -U www-data > top.txt
在該例中,咱們以用戶 www-data
每三秒記錄一次進程的日誌信息。若是你想更加明確的指定目標,可使用 -p 1, 2, 3
,而不是 -U username
,其中 一、2 和 3 是 pid(即要觀測進程的進程ID)。
網頁服務器爲配有以 FastCGI processes 方式運行的 Python 2.5 的 Lighttpd 。儘管數據庫的日誌也是頗有用的信息,但咱們沒有記錄該進程(PostgreSQL)的日誌。
另外一個有用的工具是 vmstat
,特別是 swap 列顯示了有多少內存進行了交換。交換的意思是你沒有足夠的內存,它是一種性能殺手。要想反覆運行 vmstat 的話
,必須指定每次檢查間隔的秒數。如:
vmstat 2
httperf
只是簡單地向某個 URL 發出簡單的 GET
請求,並下載 html 文本(但不包括任何媒體文件)。對公共/匿名(public/anonymous)網頁發起的訪問請求是件輕鬆的事情,但若是要訪問須要登陸的頁面怎麼辦呢?
httperf
能夠傳遞請求頭部信息。Django 身份校驗( authentication)(由 django.contrib.auth
提供)使用的進程依賴於在客戶端 cookie 中所保存的進程 id 。而客戶端在請求的頭部信息中傳遞 cookie 。你能夠看到這一切是如何進行的。
登陸站點,查看 cookies 信息。其中應該有個相似 sessionid=97d674a05b2614e98411553b28f909de
的數值。要經過 httperf 傳遞該 cookie,可使用 --add-header
參數選項。如:
httperf ... --add-header='Cookie: sessionid=97d674a05b2614e98411553b28f909den'
當心頭部信息以後的 n
。若是少了該字符,你的每一個請求可能都會返回超時信息。
考慮到這一點,咱們測試了網站的兩個網頁:
主頁: 對主頁的匿名訪問
「牆」: 對某個「牆」已認證訪問,該網頁包括從數據庫獲取的內容
對於匿名用戶來講,主頁基本上是靜態的,它只是簡單的渲染某個模板而無需數據庫的任何數據。
「牆」頁面則很是動態,包括了從數據庫獲取的主要數據。該模板在被渲染時,針對不一樣時區用戶的日期設置「刪除」了指向某些物件的連接,等等。某些「牆」包含了大約50個物件,在被優化前,大約要發起 80 條數據庫查詢。
第一次測試時,咱們運行了兩個能夠從 Django 接受請求 FastCGI 後端。
Home: 175 req/s (即每秒請求數量)Wall: 8 req/s.
第一個配置優化是使用 GZipMiddleware
激活輸出的 gzip 壓縮。性能輕微提升,沒有大的變化。但不管如何爲了節約帶寬,這麼作仍是值得的。
Home: 200 req/s.
Wall: 8 req/s.
而後,咱們將 FastCGI 後端的數量從 2 個提高到 5 個。這項改進減小了 500 響應的數量,由於更多的請求能夠由額外的後端來處理。
Home: 200 req/s.
Wall: 11 req/s.
從 2 到 5 的改進很是不錯,所以咱們決定將 FastCGI 後端數量提高到 10 。性能卻顯著地 降低 了。
經查看服務器上的 vmstat
,能夠看到緣由是出現了內存交換。太多的進程,每一個都爲 Python 使用了內存,致使 VPS 內存耗盡,從而不得不從硬盤往返交換內存。
Home: 150 req/s.
Wall: 7 req/s.
基於此,咱們將 FastCGI 後端數量降回 5 以進行更多測試。
「牆」頁面的性能使人失望,所以咱們開始進行優化。咱們所作第一件事情是分析代碼以肯定時間都被花費在何處。
使用一些簡單的 分析中間件 以後,很清楚地發現時間被消耗在數據庫查詢之上。「牆」頁面包括許多查詢,且數量與其所包含的物件數量呈正比。測試牆頁面上引起了大約 80 個查詢。毫無疑問其性能是糟糕的。
經過優化物件附加媒體的處理方式,咱們直接給每一個物件剔除了一次查詢。該措施稍微地減小了請求所需時間,所以也提升了每秒可處理的查詢數量。
Wall: 12 req/s.
致使低效的另外一個緣由是不管頁面是否被請求,對每一個物件的內容都應用了多個過濾器(Filter)。經咱們修改,被過濾內容的 HTML 輸出都被存儲在物件當中,節約了頁面被查閱時的須要進程。這又帶來一點小小改進。
Wall: 13 req/s.
經過減小數據庫查詢,咱們以修改用戶配置文件(用於顯示是誰將該物件粘貼到牆上)的獲取方式爲每一個物件剔除了一次查詢。此次修改又提升了很多。
Wall: 15 req/s.
這場測試的最後一次優化目標是減小獲取物件所附加媒體的查詢數量。咱們再一次削減了一些查詢,稍微地提升了性能。
Wall: 17 req/s.
在儘量地減小查詢以後,接下來要進行一些緩衝工做。獲取緩存數據一般比查詢數據庫更加快捷,所以咱們期待性能有一個顯著提高。
對整個頁面的輸出進行緩存是沒有意義的,由於每一個頁面根據發出請求的用戶不一樣而大相徑庭。只有當用戶對同一頁面的兩次請求之間,狀況沒有發生任何變化,纔可能出現緩存命中。
對牆、物件及用戶的列表進行緩存的做用更大。被緩存的數據將被用於從同一用戶發出的多個請求,及在對於牆壁的不一樣程度和不一樣用戶訪問之間共享。這未必是巨大的勝利,由於每一個「牆」極可能只有極少數的用戶,而數據必須在高速緩存中停留足夠長的時間以被別人獲取。
在這種狀況下,咱們簡化的 httperf
測試將會被極大地誤導。每一個請求都由同一用戶發出,所以緩存命中幾乎是100%,而性能將所以極高!這反映不出真實世界的站點使用狀況,所以咱們最好進行一些更好的測試。
目前咱們尚未使用緩存,由於站點能夠輕鬆地應對的當前活動水平,但一旦 Hey! Wall 流行起來,這將是咱們的下一個步驟。
提供每秒 17 次請求相應看起來仍然很是少,但將該數據翻譯成站點的實際用戶量是很是有趣的事情。顯然,這數據不包括提供像圖片、CSS 和 JavaScript 文件之類的媒體文件服務。相對來講,媒體文件個頭要大一些,但它們直接由 Lighttpd (而不是 Django)處理,並提供了 Expires
頭部信息來容許客戶端對它們進行緩存。不過,爲了在測試中更好地進行評估,咱們仍是須要對服務器進行一些處理。
如今說採用何種通用模式還爲時過早,所以我說的只能是猜想。請容許我這麼說!
我將假定每一個用戶平均訪問三個「牆」,並按順序查看它們的內容,暫停10至20秒時間以閱讀新的評論,或查看一些照片和打開一些連接。該用戶天天進行三次這種操做。
只看牆頁面,不看媒體的話,用戶將天天對牆頁面發起 9 次訪問請求。每一個用戶一次只能發起一次訪問請求,所以在時間上,任何一秒內 17 個用戶能夠同時進行該操做。一分鐘內,用戶只發出3次訪問請求,所以17個併發用戶只用去了60 秒中的 3 秒(或20秒中的1秒)。
若是一段時間內用戶的請求分佈是徹底平衡的(提示:不可能的!),那也就意味着每分鐘能夠有 340 用戶(17 * 20)訪問該網站。延續這個不真實的例子,咱們能夠說天天有 1440 分鐘,而每一個用戶天天訪問網站 3 分鐘,所以該網站能夠應對大約 163,000 個用戶。這對於一個每個月 20 美圓的 VPS 來講已經很是棒了!
爲了更多統計一下這些數字,讓咱們假定天天6小時內,咱們每分鐘應對 200 個併發用戶,另 6 個小時內(每分鐘)應對 100 個併發用戶,剩下的 12 小時內(每分鐘)應對 10 個併發用戶。基於每秒 17 次請求的最大負荷,網站天天仍然能夠應對的大約 115,000 個用戶。
我確信這些數字並不虛假和荒謬。若是有人在評論中提出更好的評估方案或者真實世界的數據,我將很是感興趣。