--提升性能的同時為你節(jié)約10倍以上成本
From: http://blog.sina.com.cn/iyangjian
一,Cache, 王道也
二,Cache 基本原理介紹
三,我劃分的3個刷新級別
四,我對HTTP協(xié)議做的一點創(chuàng)新(?maxage=6000000)
五,Yslow優(yōu)化網(wǎng)站性能的14條軍規(guī)點評
六,上線了 !=
七,提速度同時節(jié)約成本方法匯總
-----------------------------------------------------------------------------------------
一,Cache,王道也
我覺得系統(tǒng)架構(gòu)不應(yīng)該僅僅是搭建一個強硬的能承受巨大并發(fā)壓力的后臺,前端頁面也是需要架構(gòu)的而且同等重要,不理解前臺的的后臺工程師是不合格的。中國人講究鋼柔相濟,后臺強硬只能說你內(nèi)功深厚,前端用的巧,那叫四兩撥千斤。
一般后臺工程師很少關(guān)心前端如何使用自己的資源,而前端工程師,不知道自己的一個簡單的用法會對后端造成多大影響。我會給出一些數(shù)據(jù),來震撼下你的眼球。
二,Cache 基本原理介紹 (參考Caching Tutorial)
為什么使用Cache?
1,減少延遲,讓你的網(wǎng)站更快,提高用戶體驗。
2,避免網(wǎng)絡(luò)擁塞,減少請求量,減少輸出帶寬。
補充一個cache的原則:不更新的資源就不應(yīng)該讓它再次產(chǎn)生HTTP請求,如果強制產(chǎn)生了請求,那么就看看能否返回304。
Cache的種類?
瀏覽器Cache,代理Cache,網(wǎng)關(guān)Cache。
后端還有 disk cache ,server cache,php cache,不過不屬于我們今天討論范圍。
Cache如何工作的?
1,如果響應(yīng)頭告訴cache別緩存它,cache不對它做緩存;
2,如果請求需要驗證的或者是需要安全性的,它將不被緩存;
3,如果響應(yīng)頭里沒有ETag或Last-Modifed header這類元素,而且也沒有任何顯式的信息告訴如何對數(shù)據(jù)保鮮,則它被認(rèn)為不可緩存。
4,在下面情況下,一個緩存項被認(rèn)為是新鮮的(即,不需到原server上檢查就可直接發(fā)送給client):
5,如果有一項過期了,它將會讓原server去更新它,或者告訴cache這個拷貝是否還是可用的。
怎么控制你的Cache?
Meta tags :在html頁面中指定,這個方法只被少數(shù)瀏覽器支持,Proxy一般不會讀你html的具體內(nèi)容然后再做cache決策的。
Pragma: no-cache : 一般被大家誤用在http響應(yīng)頭中,這不會產(chǎn)生任何效果。而實際它僅僅應(yīng)該用在請求頭中。不過google的Server: GFE/1.3 響應(yīng)中卻這樣用,難道人家也誤用了呢。
Date: 當(dāng)前主機GMT時間。
Last-Modified : 文件更新GMT時間,我在響應(yīng)頭中帶上這個元素的時候,通常瀏覽器在cache時間內(nèi)再發(fā)請求都會稍帶上If-Modified-Since,讓我們判斷需要重新傳輸文件內(nèi)容,還是僅僅返回個304告訴瀏覽器資源還沒更新,需要緩存策略的服務(wù)器肯定都得支持的。有了這個請求,head請求在基本沒太多用處了,除非在telnet上調(diào)試還能用上。
If-Modified-Since :
Etag: 標(biāo)識資源是否發(fā)生變化,etag的生成算法各是各樣,通常是用文件的inode+size+LastModified進行Hash后得到的,可以根據(jù)應(yīng)用選擇適合自己的。Last-Modified 只能精確到秒的更新,如果一秒內(nèi)做了多次更新,etag就能派上用場。貌似大家很少有這樣精確的需求,浪費了http header的字節(jié)數(shù),建議不要使用。
Expires :
Cache-Control:
這個是http 1.1中為了彌補 Expires 缺陷新加入的,現(xiàn)在不支持http 1.1的瀏覽器已經(jīng)很少了。
max-age: 指定緩存過期的相對時間秒數(shù),max-ag=0或者是負(fù)值,瀏覽器會在對應(yīng)的緩存中把Expires設(shè)置為1970-01-01 08:00:00 ,雖然語義不夠透明,但卻是我最推薦使用的。
s-maxage: 類似于max-age,只用在共享緩存上,比如proxy.
public: 通常情況下需要http身份驗證的情況,響應(yīng)是不可cahce的,加上public可以使它被cache。
no-cache: 強制瀏覽器在使用cache拷貝之前先提交一個http請求到源服務(wù)器進行確認(rèn)。這對身份驗證來說是非常有用的,能比較好的遵守 (可以結(jié)合public進行考慮)。它對維持一個資源總是最新的也很有用,與此同時還不完全喪失cache帶來的好處,因為它在本地是有拷貝的,但是在用之前都進行了確認(rèn),這樣http請求并未減少,但可能會減少一個響應(yīng)體。
no-store:
must-revalidate: 強制瀏覽器嚴(yán)格遵守你設(shè)置的cache規(guī)則。
proxy-revalidate: 強制proxy嚴(yán)格遵守你設(shè)置的cache規(guī)則。
用法舉例:
其他一些使用cache需要注意的東西,不要使用post,不要使用ssl,因為他們不可被cache,另外保持url一致。只在必要的地方,通常是動態(tài)頁面使用cookie,因為coolie很難cache。至于apache如何支持cache和php怎么用header函數(shù)設(shè)置cache,暫不做介紹,網(wǎng)上資料比較多。
如何設(shè)置合理的cache時間 ?
/uploadfiles/2009/08021041734.gif?1230015976759
拿我分時圖舉例,我們需要的更新頻率是1分鐘。但為了每次都拿到最新的資源,我們在后面加了個隨機數(shù),這個數(shù)在同一秒內(nèi)的多次刷新都會變化。我們的js雖然能夠很好的控制,一分鐘只請求一次,但是如果用戶點了刷新按紐呢?這樣的調(diào)用是完全cache無關(guān)的,連返回304的機會都沒有。
試想,如果很多人通過同一個代理出去的,那么所有的請求都會穿透代理,弄不好被網(wǎng)管封掉了。如果我們做只做一秒的cache,對直接訪問源服務(wù)器的用戶沒太多影響,但對于代理服務(wù)器來說,他的請求可能會從10000 req/min 減少為 60 req/min ,這是160倍。
對于我們行情圖片這樣的情況,刷新頻率為1分鐘,比較好的做法是把后面的隨機數(shù)(num)修改為 num=t-t%60 其中t是當(dāng)前時間戳,這樣你一分鐘內(nèi)刷這個url是不變的,下一分鐘會增加1,會再次產(chǎn)生一個新請求。而我的max-age設(shè)置為默認(rèn)59秒,即使設(shè)置120秒其實也沒什么影響。可能你會說萬一趕上臨界點可能拿不到最新的數(shù)據(jù),其實對用戶來說,用那個多變的隨即數(shù)和我這個分鐘級的隨即數(shù),看到的效果是相同的下面我給你分析一下:如果用戶打開了我們的分時間頁面,當(dāng)前隨即數(shù)對他來說是新的,所以他會拿到一個當(dāng)前最新的圖片,然后他點了刷新按紐,用戶會產(chǎn)生http請求,即使url沒變,服務(wù)器有最新圖片也一定會返回,否則返回304,一分鐘后js刷新圖片,分鐘數(shù)加了1,會得到全新資源。這和那個隨時變化的隨即數(shù)效果有區(qū)別嗎?都拿到了最新的數(shù)據(jù),但是卻另外收益了cache帶來的好處,對后端減少很多壓力。
三,我劃分的3個刷新級別
名詞解釋 全新請求: url產(chǎn)生了變化,瀏覽器會把他當(dāng)一個新的資源(發(fā)起新的請求中不帶If-Modified-Since)。
1,在地址欄中輸入http://sports.sinajs.cn/today.js?maxage=11地址按回車。重復(fù)n次,直到cache時間11秒過去后,才發(fā)起請求,這個請求是全新的,不帶If-Modified-Since。
2,按F5刷新.
3, ctrl+F5 ,總會發(fā)起一個全新的請求。
下面是按F5刷新的例子演示: http://sports.sinajs.cn/today.js?maxage=11
( 如果這個值大于瀏覽器最大cache時間maxage,將以瀏覽器最大cache為準(zhǔn))
----------------------------------------------------------發(fā)起一個全新請求
GET /today.js?maxage=11 HTTP/1.1
Host: sports.sinajs.cn
Connection: keep-alive
HTTP/1.x 200 OK
Server: Cloudia
Last-Modified: Mon, 24 Nov 2008 11:03:02 GMT
Cache-Control: max-age=11
Content-Length: 312
Connection: Keep-Alive
---------------------------------------------------------- 按F5刷新
GET /today.js?maxage=11 HTTP/1.1
Host: sports.sinajs.cn
Connection: keep-alive
If-Modified-Since: Mon, 24 Nov 2008 11:03:02 GMT
Cache-Control: max-age=0
HTTP/1.x 304 Not Modified
Server: Cloudia
Connection: Keep-Alive
Cache-Control: max-age=11
----------------------------------------------------------
繼續(xù)按F5刷新n次.......
這11秒內(nèi)未產(chǎn)生http請求.直到11秒過去了...............
----------------------------------------------------------按F5刷新
GET /today.js?maxage=11 HTTP/1.1
Host: sports.sinajs.cn
Connection: keep-alive
HTTP/1.x 200 OK
Server: Cloudia
Last-Modified: Mon, 24 Nov 2008 11:03:02 GMT
Cache-Control: max-age=11
Content-Encoding: deflate
Content-Length: 312
Connection: Keep-Alive
Content-Type: application/x-javascript
----------------------------------------------------------按F5刷新
GET /today.js?maxage=11 HTTP/1.1
Host: sports.sinajs.cn
Connection: keep-alive
If-Modified-Since: Mon, 24 Nov 2008 11:03:02 GMT
Cache-Control: max-age=0
HTTP/1.x 304 Not Modified
Server: Cloudia
Connection: Keep-Alive
Cache-Control: max-age=11
----------------------------------------------------------
四,我對HTTP協(xié)議做的一點創(chuàng)新(?maxage=6000000)
上面看到了url后面有
1,可以控制HTTP header的的 max-age 值。
2, 讓用戶為每個資源靈活定制精確的cache時間長度。
3, 可以代表資源版本號。
首先談?wù)搶蠖说挠绊懀?BR>服務(wù)器實現(xiàn)那塊,不用再load類似mod_expires,mod_headers 這樣額外的module,也不用去加載那些規(guī)則去比較,它屬于什么目錄,或者什么文件類型,應(yīng)該cache多少時間,這樣的操作是需要開銷的。
再說說對前端的影響:
比如同一個分時行情圖片,我們的分時頁中需要1分鐘更新,而某些首頁中3分鐘更新好。不用js控制的話,那我cache應(yīng)該設(shè)置多少呢?
另一種情況是,我們?yōu)榱薱ache,把某個圖片設(shè)置了一個永久cache,但是由于需求,我必須更新這個圖片,那怎么讓用戶訪問到這個更新了的圖片呢?從yahoo的資料和目前所有能找到的資料中都描述了同一種方法,更改文件名字,然后引用新的資源。 我覺得這方法太土, 改名后,老的還不能刪除,可能還有地方在用,同一資源可能要存兩份,再修改,又得改個名,存3份,不要不把inode當(dāng)資源。我就不那樣做,只需要把maxage=6000000 修改成 maxage=6000001 ,問題就解決了。
maxage=6000000 所產(chǎn)生的威力 (內(nèi)存塊消耗減少了250倍
體育那邊要上一個新功能,一開始動態(tài)獲取那些數(shù)據(jù),我覺得那樣太浪費動態(tài)池資源,就讓他們把xml文件到轉(zhuǎn)移到我的js池上來,為了方便,他們把那個84k的flash文件也放在了一起,而且是每個用戶必須訪問的。說實在的,我不歡迎這種大塊頭,因為它不可壓縮,按正常來說,它應(yīng)該代表一個3M的文件。我的服務(wù)器只這樣設(shè)計的,如果一次發(fā)送不完的就暫存在內(nèi)存里,每個內(nèi)存塊10k,如果不帶參數(shù)默認(rèn)maxage=120 。 我發(fā)現(xiàn),由于這個文件,10w connections的時候,我消耗了10000個內(nèi)存塊。我自己寫的申請連續(xù)內(nèi)存的算法也是消耗cpu地,一個84k的文件,發(fā)送一次后,剩余的64k就應(yīng)該能裝的下,于是我把最小內(nèi)存塊大小改為64K。 這樣消耗10w conn的時候消耗1500個左右內(nèi)存快,雖然內(nèi)存消耗總量沒怎么變小,但是它能更快的拿到64K的連續(xù)內(nèi)存資源,cpu也節(jié)約下來了。接下來我讓meijun把所應(yīng)用的flash資源后面加上maxage=6000000 (大概=79天,瀏覽器端最長cache能達(dá)到著個就不錯了), 10w connections的時候,只消耗了不到40個內(nèi)存塊,也就是說內(nèi)存塊消耗減少了250倍
五,Yslow優(yōu)化網(wǎng)站性能的14條軍規(guī)點評
其中黑色部分,跟后端是緊密相連的,在我們的內(nèi)容中都已經(jīng)涉及到了,而且做了更深入的討論。蘭色部分,5,6,7是相關(guān)頁面執(zhí)行速度的,構(gòu)建前端頁面的人應(yīng)該注意的。 11屬于避免使用的方法。 紅色部分我著重說一下:
gzip 我不推薦使用,因為有些早期IE支持的不好,它的表現(xiàn)為直接用IE訪問沒問題,用js嵌進去,就不能正常解壓。這樣的用戶占比應(yīng)該在2%左右。這個問題我跟蹤了近一個月,差點放棄使用壓縮。后來發(fā)現(xiàn)我以前用deflate壓縮的文件卻能正常訪問。 改用deflate問題解決。apache 1.x使用mod_gzip ,到了 2.x 改用cmod_deflate,不知道是否跟這個原因有關(guān)。 另外對于小文件壓縮來說,deflate 可比 gzip 省不少字節(jié)。
減少 DNS 查詢: 這里也是有個取舍的,一般瀏覽器最多只為一個域名創(chuàng)建兩個連接通道。 如果我一個頁面嵌了 image.xx.com 的很多圖片,你就會發(fā)現(xiàn),圖片從上往下一張張顯示出來這個過程。這造成了瀏覽器端的排隊。 我們可以通過增加域名提高并發(fā)度,例如 image0.xx.com ,image1.xx.com ,image2.xx.com,image3.xx.com 這樣并發(fā)度就提上去了,但是會造成很多cache失效,那很簡單,假如我們對文件名相加,對4取mod,就能保證,某個圖片只能通過某個域名進行訪問。不過,我也很反對一頁面請求了數(shù)十個域名,很多域名下只有一到兩個資源的做法,這樣的時間開銷是不劃算的。
另外,我在這里再添一個第15條:錯開資源請求時間,避免瀏覽器端排隊。
隨著ajax的廣泛使用,動態(tài)刷新無處不在,體育直播里有個頁面調(diào)用了我一個域名下的6個文件,3個js,3個xml。刷新頻率大致是兩個10秒的,兩個30秒的,兩個一次性載入的。觀察發(fā)現(xiàn)正常響應(yīng)時間都在7ms,但是每過一會就會出現(xiàn)一次在100ms以上的,我就很奇怪,服務(wù)器負(fù)載很輕呢。meijun幫我把刷新時間錯開,11秒的,9秒的,31秒的,這樣響應(yīng)在100ms以上的概率減少了好幾倍,這就是所謂的細(xì)節(jié)決定成敗吧。
1. 盡可能的減少 HTTP 的請求數(shù)
2. 使用 CDN(Content Delivery Network)
3. 添加 Expires 頭(或者 Cache-control )
4. Gzip 組件
5. 將 CSS 樣式放在頁面的上方
6. 將腳本移動到底部(包括內(nèi)聯(lián)的)
7. 避免使用 CSS 中的 Expressions
8. 將 javascript 和 CSS 獨立成外部文件
9. 減少 DNS 查詢
10. 壓縮 javascript 和 CSS (包括內(nèi)聯(lián)的)
11. 避免重定向
12. 移除重復(fù)的腳本
13. 配置實體標(biāo)簽(ETags)
14. 使 AJAX 緩存
六,上線了 !=
奧運期間我按1500w~2000w connections在線,設(shè)計了一套備用系統(tǒng),現(xiàn)在看來,如果用戶真達(dá)到了這個數(shù)目我會很危險,因為有部分服務(wù)器引入了32bit的centos 5未經(jīng)實際線上檢驗,而我當(dāng)時簡單的認(rèn)為它應(yīng)該和centos 4表現(xiàn)出一樣的特性。所以現(xiàn)在未經(jīng)過完全測試的lib庫和新版本,我都很謹(jǐn)慎的使用。沒在真實環(huán)境中檢驗過,不能輕易下結(jié)論。
很多項目組好象不停的忙,做新項目,上線后又繼續(xù)下個新項目,然后時不時的轉(zhuǎn)過頭去修理以前的bug。如果一個項目上線后,用戶量持續(xù)上升,就應(yīng)該考慮優(yōu)化了,一個人訪問,和100w人訪問,微小的修改對后端影響是不能比較的,不該請求的資源就讓它cache在用戶的硬盤上,用戶訪問塊了,你也省資源。上線僅僅代表可以交差了而已,對于技術(shù)人員來說持續(xù)的對一個重要項目進行跟蹤和優(yōu)化是必要的。
七,提速度同時節(jié)約成本方法匯總
1,編寫節(jié)約的HTTP服務(wù)器 (高負(fù)載下速度明顯提升,節(jié)約5~10倍服務(wù)器)
對一些重要的服務(wù)器量身定做。或者選用比較高效的開源軟件進行優(yōu)化。
2,不同服務(wù)混合使用
如果我們一臺服務(wù)器只支持30w conn的話,那么剩余的75% cpu資源,95%的內(nèi)存資源,和幾乎所有的磁盤資源都可以部署動態(tài)池系統(tǒng),我覺得DB對網(wǎng)卡中斷的消耗還是有限的,我也不用新買網(wǎng)卡了。
3,對于純數(shù)據(jù)部分啟用新的域名(速度有提升,上行帶寬節(jié)約1倍以上)
比如我們另外購買了sinajs.cn 來做數(shù)據(jù)服務(wù),以避免cookie,節(jié)約帶寬. Cookie不但會浪費服務(wù)器端處理能力,而且它要上行數(shù)據(jù),而通常情況上行比下行慢。
4, 使用長連接 (速度明顯提升,節(jié)約帶寬2倍以上,減少網(wǎng)絡(luò)擁塞3~無數(shù)倍)
對于一次性請求多個資源,或在比較短的間隔內(nèi)會有后續(xù)請求的應(yīng)用,使用長連接能明顯提升用戶體驗,減少網(wǎng)絡(luò)擁塞,減少后端服務(wù)器建立新連接的開銷。
5,數(shù)據(jù)和呈現(xiàn)分離,靜態(tài)數(shù)據(jù)和動態(tài)數(shù)據(jù)分離 (速度明顯提升,同時節(jié)約3倍帶寬)
div+css 數(shù)據(jù)和呈現(xiàn)分離以后,據(jù)說文件大小能降到以前的1/3。
把頁面中引用的js文件分離出來,把動態(tài)部分和靜態(tài)部分也分離開來。
6,使用deflate壓縮算法 (速度明顯提升,節(jié)約3.33倍帶寬)
一般來說壓縮過的文件大小不到以前的30% 。
將上面分離出來的數(shù)據(jù)進行壓縮(累計節(jié)約帶寬10倍)。
7, 讓用戶盡可能多的Cache你的資源 (速度明顯提升,節(jié)約3~50倍服務(wù)器資源和帶寬資源)
將上面分離出來的css和不經(jīng)常變動的js數(shù)據(jù)部分cache住合適的時間。(理想情況,累計節(jié)約帶寬30~500倍) 。
以上改進可以讓速度大幅度提升的同時,服務(wù)器資源節(jié)約 5~20 倍 ,減少網(wǎng)絡(luò)擁塞3~無數(shù)倍, 上行帶寬節(jié)約1倍以上,下行帶寬節(jié)約30~500倍,甚至更多。
新聞熱點
疑難解答