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