前言
網站設計的優化是一個很大的話題,有一些通用的原則,也有針對不同開發平台的一些建議。這方面的研究一直沒有停止過,我在不同的場合也分享過這樣的話題。
作為通用的原則,雅虎的工程師團隊曾經給出過35個最佳實踐。這個列表請參考
Best Practices for Speeding Up Your Web Site http://developer.yahoo.com/performance/rules.html
同時,他們還發布了一個相應的測試工具Yslow http://developer.yahoo.com/yslow/
我強烈推薦所有的網站開發人員都應該學習這些最佳實踐,並結合自己的實際項目情況進行應用。
接下來的一段時間,我將結合ASP.NET這個開發平台,針對這些原則,通過一個系列文章的形式,做些講解和演繹,以幫助大家更好地理解這些原則,並且更好地使用他們。
准備工作
為了跟隨我進行后續的學習,你需要准備如下的開發環境和工具
- Google Chrome 或者firefox ,並且安裝 Yslow這個擴展組件.請注意,這個組件是雅虎提供的,但目前沒有針對IE的版本。
- https://chrome.google.com/webstore/detail/yslow/ninejjcohidippngpapiilnmkgllmakh
- https://addons.mozilla.org/en-US/firefox/addon/yslow/
- 你應該對這些瀏覽器的開發人員工具有所了解,你可以通過按下F12鍵調出這個工具。
- Visaul Studio 2010 SP1 或更高版本,推薦使用Visual Studio 2012
- 你需要對ASP.NET的開發基本流程和核心技術有相當的了解,本系列文章很難對基礎知識做普及。
本文要討論的話題
緩存!這是一條多么重要的原則。幾乎所有的網站優化的書籍或者文章中都會提到這個原則,而且目前在運行的一些網站都或多或少地使用到了這個技術。這個原則的相關概念可以參考這里:http://developer.yahoo.com/performance/rules.html#expires
我們還是以博客園的主頁為例,通過簡單的監控就能發現,他們大量地使用了緩存的功能(針對不同資源,緩存的策略可能略有不同,請注意觀察max-age的值,以秒為單位,有興趣的同學可以計算一下)
接下來,我會從幾個方面,和大家來談談緩存的問題
1.緩存的概念
緩存是服務器與客戶端(或者中間的代理服務器)之間的一種約定,利用緩存可以明顯地減少重復從服務器下載內容的次數,這樣就可以極大地提高吞吐量以及響應速度。在HTTP 1.1協議中,對於緩存有明確的,詳細的說明:http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
由於緩存如此重要,所以對於很多瀏覽器而言(尤其是現代瀏覽器),他們默認就會嘗試對靜態內容進行緩存。(這里先賣一個關子,大家覺得默認情況下它會緩存多長時間呢?)
下圖可以很好地揭示緩存的作用
這是我兩次訪問一個簡單的頁面的情形,第一次(我用顏色選中的8個請求)的時候,因為沒有緩存,所以所有的內容資源(動態的和靜態的)都需要下載。瀏覽器會根據情況對其進行緩存(通常就是對於靜態內容它會有一個緩存的策略,后面再細述)。但是第二次訪問,就完全不一樣了,只有第1個請求是需要下載內容的(這是一個動態頁面),其他的7個內容都是無需下載的(你可以看到狀態碼是304,而且Body都是0)
2.靜態資源的緩存及其設置
默認情況下,瀏覽器(至少絕大多數現代瀏覽器)都會對網站中的靜態內容進行緩存。常見的靜態內容包括
- HTM,HTML文件
- JAVASCRIPT文件
- CSS文件
- 圖片文件
如果服務器端不做任何的設置,那么默認情況下它緩存多長時間呢?答案是:可能很久,這個取決於兩個因素
- 瀏覽器緩存中的這個文件是否被清理(可能是用戶手工地進行清理,有的公司也可能會設置統一的策略自動在瀏覽器關閉的時候清理)
- 服務器對應的文件是否有更新
這里就要講到那個304的狀態碼了. 這個狀態碼的意思是:Not Modified(未更改)。為什么會產生這樣的狀態碼呢?其實可以通過下面三個截圖來理解清楚
第一次請求某靜態資源的時候,服務器會在返回內容(狀態碼為200)的同時,包含一個特殊的Header,叫做Last-Modified ,這個Header會記錄在服務器端該文件最后修改的時間。如下圖所示
然后,瀏覽器會將此文件緩存起來。
接下來如果需要第二次訪問這個文件,瀏覽器發起的請求中,也會包含一個特殊的Header
這個Header的意思是說,要檢查從這個時間后是否有修改。如果該文件沒有修改過,則服務器就會返回304這個狀態碼,並且不會返回任何內容。
瀏覽器收到了304這個狀態碼的話,就會使用自己已經緩存的那個版本進行呈現。
我們當然也可以靜態資源的緩存策略進行人工的干預,這個可以通過兩個途徑來實現
- 通過IIS 管理界面。選擇某個站點,然后在右側的功能面板中,選擇“Http Response Header”,如下圖所示
然后在功能頁面中,點擊右側的“Set Common Header…” 這個Action
然后在彈出的對話框中設置Expire Web Content的選項
這里默認會有三個策略:立即過期,在一個相對時間范圍內過期,在一個絕對時間后過期。你可以根據自己的需求進行設置。
【備注】這些設置僅僅影響靜態資源。
- 直接修改web.config文件
上面提到的通過IIS管理界面對靜態資源配置到期時間(其實也就是指一個緩存的時間),這個做法適合給管理員使用。作為開發人員,如果你希望自己來控制這些選項,那么可以直接在網站的配置文件(web.config)中添加相關的配置即可。
<system.webServer> <staticContent> <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="1.00:00:00" /> </staticContent> </system.webServer>
需要注意的是,在之前的截圖中,有些特殊的腳本,它們的緩存時間是不受這個配置影響的。
他們是默認緩存一年的.這些特殊的腳本(或者樣式表)其實是多個文件的組合,如果對這一點不太了解,請參考我之前的一篇文章
優化網站設計(一):減少請求數
3.動態資源的緩存及其設置
我們上面討論到了靜態資源的緩存及其設置,但是對於現在的大部分網站來說,光有靜態資源是遠遠不夠的,我們會有很多動態資源,典型的就是動態頁面(例如aspx頁面),那么對於這種動態資源,是否也有可能進行一定的緩存呢?
答案是肯定的,但對於動態資源的緩存細節相當多,恐怕要超出本文的范疇。我強烈推薦大家詳細閱讀MSDN中的這篇文檔 :
http://msdn.microsoft.com/en-us/library/aa478965.aspx (ASP.NET Caching: Techniques and Best Practices)
我這里為大家總結一下對動態資源進行緩存的幾種做法
- 頁面緩存,在頁面中使用OutputCache,適合於對整個頁面所有內容做緩存
- 片段緩存,在用戶控件中使用OutputCache,適合於對頁面的一部分(通常封裝為一個用戶控件)內容做緩存
- 在MVC中,同樣可以在Action上面使用OutputCache這個Attribute來定義緩存設置
- 數據緩存,在代碼中訪問Cache對象,適合對多個頁面中可能會用到的共享數據做精細的緩存控制
值得注意的是,這三種做法並非是互斥的,在實際的應用中,他們會相互結合起來使用。
另外,除了給頁面或者控件設置OutputCache(為了靈活起見,建議結合CacheProfile)之外,如果確實某些緩存的設置需要動態決定,也可以采用如下的方式來實現同樣的效果
Response.Cache.SetExpires(DateTime.Now.AddSeconds(60)); Response.Cache.SetCacheability(HttpCacheability.Public); Response.Cache.SetValidUntilExpires(false); Response.Cache.VaryByParams["Category"] = true; if (Response.Cache.VaryByParams["Category"]) { //... }
關於Response.Cache的所有屬性和操作,有興趣的可以參考 http://msdn.microsoft.com/EN-US/library/system.web.httpcachepolicy.aspx
【備注】本文之前提到的Bundle默認設置為1年的過期時間就是通過這種方式來實現的。
4.緩存的反面
緩存是一種很有用的技術,幾乎所有人都知道它的好處。現在的開發平台都比較強大,讓我們可以有比較簡單的方式來實現緩存。
但是,緩存有它的一些代價,或者說有反面的一些問題需要考慮。典型的問題在於
- 版本控制。
- 版本是一個問題,不是嗎?典型的問題是:如果你緩存了一個頁面,但這個頁面的數據其實是發生了變化,那么如果你還是以緩存的版本給用戶呈現,是否有問題呢?
- 如何解決版本問題呢?這里會有一個緩存依賴的概念需要了解,
- 容量問題。
- 靜態資源通常是緩存在客戶端的(除非服務器端做特殊設置,IIS 7開始支持在服務器端——甚至內核模式——中緩存內容),他們通常對服務器影響不大。
- 但動態資源的緩存,通常是緩存在服務器端的(或者客戶端和服務器端各有一份),所以需要占用服務器的內存空間。
- 如果不加以限制(或者不做周全的考慮),則很可能會因為緩存了過多的內容,而導致服務器的內存出現爭用的問題。
- 這里留一個問題,請問ASP.NET應用程序的緩存功能,默認最多可以使用多少服務器內存?
- 這里所謂的周全考慮,是指我們需要對緩存的必要性進行評估。應該只緩存那些確實有必要緩存的內容。這個說來容易,實際上做起來卻是不那么容易的。
- 我們需要評估諸如緩存命中率這樣一些性能指標來評估緩存的設置是否恰當