Web應用程式效能最佳化黃金法則:先優化前端程式(front-end)的效能,因為這是80%或以上的最終使用者回應時間的花費所在。
法則1. 減少HTTP請求次數
80%的最終使用者回應時間花在前端程式上,而其大部分時間則花在各種頁面元素,如圖像、樣式表、腳本和Flash等,的下載上。減少頁面元素將會減少HTTP請求次數。這是快速顯示頁面的關鍵。
一種減少頁面元素數量的方法是簡化頁面設計。但是否有其他方式,能做到既有豐富內容,又能獲得快速回應時間呢?以下是這樣一些技術:
Image maps組合多個圖片到一張圖片中。總檔案大小變化不大,但減少了HTTP請求次數從而加快了頁面顯示速度。此方式只適合圖片連續的情況;同時座標的定義是煩人又容易出錯的工作。
CSS Sprites是更好的方法。它可以組合頁面中的圖片到單一檔案中,並使用CSS的background-image和background-position屬性來現實所需的部分圖片。
Inline images使用data: URL scheme來在頁面中內嵌圖片。這將增大HTML檔案的大小。組合inline images到你的(快取)樣式表是既能較少HTTP請求,又能避免加大HTML檔案大小的方法。
Combined files透過組合多個腳本檔案到單一檔案來減少HTTP請求次數。樣式表也可採用類似方法處理。這個方法雖然簡單,但沒有大規模的使用。 10大美國網站每頁平均有7個腳本檔案和2個樣式表。當頁面之間腳本和樣式表變化很大時,該方式將會遇到很大的挑戰,但如果做到的話,將能加快回應時間。
減少HTTP請求次數是效能最佳化的起點。這最提高首次訪問的效率起到很重要的作用。根據Tenni Theurer的文章Browser Cache Usage - Exposed!描述,40-60%的日常訪問是首次訪問,因此為首次訪客加快頁面訪問速度是用戶體驗的關鍵。
我們的應用:
外貿: 將首頁的幾十個小圖示合併為一個,透過CSS控制它們的顯示,減少了HTTP請求數。
法則2. 使用CDN(Content Delivery Network, 內容傳遞網路)
用戶離web server的遠近對回應時間也有很大影響。從使用者角度看,把內容部署到多個地理位置分散的伺服器將有效提高頁面裝載速度。但是該從哪裡開始呢?
作為實現內容地理分佈的第一步,不要試圖重構web應用以適應分佈架構。改變架構將導致多個週期性任務,如同步session狀態,在多個server之間複製資料庫交易。這樣縮短使用者與內容距離的嘗試可能會被應用程式架構改版延遲,或封鎖。
我們還記得80-90%的最終用戶回應時間花在下載頁面中的各種元素上,例如圖像檔案、樣式表、腳本和Flash等。與其花在重構系統這個困難的任務上,不如先分散靜態內容。這不僅能大幅減少反應時間,而且由於CDN的存在,分佈靜態內容非常容易實現。
CDN是地理上分佈的web server的集合,用於更有效率地發佈內容。通常基於網路遠近來選擇給特定使用者服務的web server。
一些大型網站擁有自己的CDN,但是使用如Akamai Technologies, Mirror Image Internet, 或 Limelight Networks等CDN服務供應商的服務將是划算的。在Yahoo!把靜態內容分佈到CDN減少了用戶影響時間20%或更多。切換到CDN的程式碼修改工作是很容易的,但能達到提高網站的速度。
我們的應用:
目前還沒用到,不過據客戶反映,廣東山東等地網絡情況比較差,如果可以將佔據主要頻寬的靜態資源透過CDN發布,相信可以大大緩解目前網站訪問速度的問題。
法則3. 增加Expires Header
網頁內容正變得越來越豐富,這意味著更多的腳本檔案、樣式表、圖片檔案和Flash。首次訪客將不得不面臨多次HTTP請求,但透過使用Expires header,您可以在用戶端快取這些元素。這在後續訪問中避免了不必要的HTTP請求。 Expires header最常用於圖像文件,但是它也應該用於腳本文件、樣式表和Flash。
瀏覽器(和代理程式)使用快取來減少HTTP請求的次數和大小,使得網頁加速裝載。 Web server透過Expires header告訴客戶端一個元素可以快取的時間長度。
如果伺服器是Apache的話,您可以使用ExpiresDefault基於當期日期來設定過期日期,如:
ExpiresDefault “access plus 10 years” 設定過期時間為從請求時間開始計算的10年。
請記住,如果使用超長的過期時間,則當內容改變時,您必須修改檔案名稱。在Yahoo!我們常把改名當作release的一個步驟:版本號內嵌在檔名中,如yahoo_2.0.6.js。
我們的應用:
外貿:在Apache配置了JS,CSS,image的緩存,如果靜態資源需要更新,則採用修改文件版本號的方案確保客戶端取得最新版本;
E網打盡:E網打盡的探頭規則(JS)是根據客戶的設置生成的,但是在相當長的一段時間內基本上不會有變化,因此,在生成規則的同時附加一個expires響應頭,盡量減少客戶端的請求和探頭規則產生的次數。
法則4. 壓縮頁元素
透過壓縮HTTP回應內容可減少頁面回應時間。從HTTP/1.1開始,web客戶端在HTTP請求中透過Accept-Encoding頭來表示支援的壓縮類型,如:
Accept-Encoding: gzip, deflate.
如果Web server檢查到Accept-Encoding頭,它會使用客戶端支援的方法來壓縮HTTP回應,會設定Content-Encoding頭,如:Content-Encoding: gzip。
Gzip是目前最受歡迎且有效的壓縮方法。其他的方式如deflate,但它效果較差,也不夠流行。透過Gzip,內容一般可減少70%。如果是Apache,在1.3版本下需使用mod_gzip模組,而在2.x版本下,則需使用mod_deflate。
Web server根據檔案類型來決定是否壓縮。大部分網站對HTML檔案進行壓縮。但對腳本檔案和樣式表進行壓縮也是值得的。實際上,對包括XML和JSON在內的任務文字資訊進行壓縮都是值得的。影像檔案和PDF檔案不應該被壓縮,因為它們本來就是壓縮格式保存的。對它們進行壓縮,不但浪費CPU,而且還可能增加檔案的大小。
因此,對盡量多的檔案類型進行壓縮是一種減少頁面大小和提高使用者體驗的簡單方法。
我們的應用:
外貿、E網打盡、K方案:600多K的ext2包,是人都會想到要去壓縮它,壓縮後的效果還不錯,只有150多K。另外,JS、CSS、HTML也盡量壓縮,要知道我們的許多客戶還在使用1M的ADSL。
法則5. 把樣式表放在頭上
我們發現把樣式表移到HEAD部分可以提高介面載入速度,因此這使得頁面元素可以順序顯示。
在許多瀏覽器下,如IE,把樣式表放在document的底部的問題在於它禁止了網頁內容的順序顯示。瀏覽器阻止顯示以免重畫頁面元素,那使用者只能看到空白頁了。 Firefox不會阻止顯示,但這意味著當樣式表下載後,有些頁面元素可能需要重畫,這導致閃爍問題。
HTML規範明確要求樣式表被定義在HEAD中,因此,為避免空白螢幕或閃爍問題,最好的辦法是遵循HTML規範,把樣式表放在HEAD中。
我們的應用:
目前還沒碰到把樣式表放在文件後面的情況吧?
法則6. 把腳本檔案放在底部
與樣式檔案一樣,我們需要注意腳本檔案的位置。我們需盡量把它們放在頁面的底部,這樣一方面能順序顯示,另方面可達到最大的並行下載。
瀏覽器會阻塞顯示直到樣式表下載完畢,因此我們需要把樣式表放在HEAD部分。而對於腳本來說,腳本後面內容的順序顯示會被阻塞,因此把腳本盡量放在底部意味著更多內容能被快速顯示。
腳本引起的第二個問題是它阻塞並行下載數量。 HTTP/1.1規格建議瀏覽器每台主機的平行下載數不超過2個。因此如果您把圖像檔案分佈到多台機器的話,您可以達到超過2個的並行下載。但是當腳本檔案下載時,瀏覽器不會啟動其他的並行下載,甚至其他主機的下載也不啟動。
在某些情況下,不是很容易就能把腳本移到底部的。如,腳本使用document.write方法來插入頁面內容。同時可能還存在域的問題。不過在很多情況下,還是有一些方法的。
一個備選方法是使用延遲腳本(deferred script)。 DEFER屬性表示腳本未包含document.write,指示瀏覽器刻繼續顯示。不幸的是,Firefox不支援DEFER屬性。在IE中,腳本可能會延遲執行,但不一定會得到需要的長時間延遲。不過從另一個角度來說,如果腳本能被延遲執行,那它就可以被放在底部了。
我們的應用:
這一點之前大家可能都沒有意識到,不過在我們的XCube XUI中我們已經實施了這條法則,相信可以進一步提升頁面的存取效能。
法則7. 避免CSS表達式
CSS表達式是功能強大的(同時也是危險的)用於動態設定CSS屬性的方式。 IE,從版本5開始支援CSS表達式,如backgourd-color: expression((new Date()).getHours()%2?”#B8D4FF”:”#F08A00”),即背景色每小時切換一次。
CSS表達式的問題是其執行次數超過大部分人的期望。不僅頁面顯示和resize時計算表達式,而且當頁面滾屏,甚至當滑鼠在頁面上移動時都會重新計算表達式。
一種減少CSS表達式執行次數的方法是一次性表達式,即當第一次執行時就以明確的數值代替表達式。如果必須動態設定的話,可使用事件處理函數來代替。如果您必須使用CSS表達式的話,請記住它們可能被執行上千次,從而影響頁面效能。
我們的應用:
目前CSS的維護工作主要由UI人員負責,他們已經盡量在避免這種情況了。
法則8. 把JavaScript和CSS放到外部檔案中
上述許多效能最佳化法則都是基於外部檔案進行最佳化。現在,我們必須問一個問題:JavaScript和CSS應該包括在外部文件,還是在頁面文件中?
在現實世界中,使用外部檔案會加快頁面顯示速度,因為外部檔案會被瀏覽器快取。如果內建JavaScript和CSS在頁面中雖然會減少HTTP請求次數,但增加了頁面的大小。另外一方面,使用外部文件,會被瀏覽器緩存,則頁面大小會減小,同時又不增加HTTP請求次數。
因此,一般來說,外部文件是更可行的方式。唯一的例外是內嵌方式對首頁更有效,如Yahoo!和My Yahoo!都使用內嵌方式。一般來說,在一個session中,主頁存取此時較少,因此內嵌方式可以取得更快的使用者回應時間。
我們的應用:
外貿、E網打盡、K計畫:ext2的程式碼作了很好的引導,目前前端開發人員都非常注意客戶端模組的封裝、重用,盡量以外部JS的方式提高程式碼的重用度,當然也要注意不要引入過多的外部資源,因為這違反了法則1。
目前CSS的封裝也不錯,但主要是針對IE系列的解決方案,可以考慮引進YAML、blueprint等CSS框架,輕鬆解決瀏覽器相容性問題。
法則9. 減少DNS查詢次數
DNS用於映射主機名稱和IP位址,一般一次解析需要20~120毫秒。為達到更高的效能,DNS解析通常被多層級地緩存,如由ISP或區域網路維護的caching server,本地機器作業系統的快取(如windows上的DNS Client Service),瀏覽器。 IE的預設DNS快取時間為30分鐘,Firefox的預設緩衝時間為1分鐘。
減少主機名稱可減少DNS查詢的次數,但可能造成並行下載數的減少。避免DNS查詢可減少回應時間,而減少並行下載數可能會增加回應時間。一個可行的折中是把內容分佈到至少2個,最多4個不同的主機名稱上。
我們的應用:
外貿:為了繞開瀏覽器對下載線程數的限制,我們對靜態資源啟用了多域名,但是這麼做違反了該法則。不過,對windows IE來說,DNS的快取可以緩解這個問題。
法則10. 最小化JavaScript程式碼
最小化JavaScript程式碼指在JS程式碼中刪除不必要的字符,從而降低下載時間。兩個流行的工具是#JSMin 和YUI Compressor。
混淆是最小化於原始碼的替代方式。象最小化一樣,它透過刪除註解和空格來減少原始碼大小,同時它還可以對程式碼進行混淆處理。作為混淆的一部分,函數名和變數名稱被替換成短的字串,這使得程式碼更緊湊,同時也更難讀,使得難於被反向工程。 Dojo Compressor (ShrinkSafe)是最常見的混淆工具。
最小化是安全的、直白的過程,而混淆則更複雜,而且容易產生問題。從對美國10大網站的調查來看,透過最小化,文件可減少21%,而混淆則可減少25%。
除了最小化外部腳本檔案外,內嵌的腳本程式碼也應該被最小化。即使腳本依照法則4被壓縮後傳輸,最小化腳本刻減少檔案大小5%或更高。
我們的應用:
我們沒有直接使用JS壓縮,但是我們使用的許多元件例如ext2、jquery等,已經在為我們實踐該法則。
法則11. 避免重新導向
重定向功能是透過301和302這兩個HTTP狀態碼完成的,如:
HTTP/1.1 301 Moved Permanently
登入後複製
<span style="color: #000000"> Location: http://example.com/newuri</span>
登入後複製
Content-Type: text/html
登入後複製
瀏覽器自動重定向請求到Location指定的URL上,重定向的主要問題是降低了使用者體驗。
一種最耗費資源、經常發生而很容易被忽視的重定向是URL的最後缺少/,如訪問http://astrology.yahoo.com/astrology將被重定向到http://astrology.yahoo .com/astrology/。在Apache下,可以透過Alias,mod_rewrite或DirectorySlash等方式來解決這個問題。
我們的應用:
經驗豐富的SA已經為我們考慮了這個問題,有興趣的同學可以看看線上環境的Apache設定檔:httpd.conf。
法則12. 刪除重複的腳本檔案
在一個頁面中包含重複的JS腳本檔案會影響效能,即它會建立不必要的HTTP請求和額外的JS執行。
不必要的HTTP請求發生在IE下,而Firefox不會產生多餘的HTTP請求。額外的JS執行,不管在IE下,還是在Firefox下,都會發生。
一個避免重複的腳本檔案的方式是使用模板系統來建立腳本管理模組。除了防止重複的腳本檔案外,該模組還可以實現依賴性檢查和增加版本號到腳本檔案名稱中,從而實現超長的過期時間。
我們的應用:
舊版的Xplatform中這個問題比較嚴重,不過相信新版的XCube不會重蹈覆轍。
法則13. 配置ETags
ETags是用來決定瀏覽器快取中元素是否與Web server中的元素相符的機制,它是比last-modified date更靈活的元素驗證機制。 ETag是用來唯一表示元素版本的字串,它需被包含在引號中。 Web server首先在response中指定ETag:
Last-Modified: Tue, 12 Dec 2006 03:03:59 GMT
登入後複製
ETag: "10c24bc-4ab-457e1c1f"
登入後複製
Content-Length: 12195
登入後複製
後來,如果瀏覽器需要驗證某元素,它使用If-None-Match頭回傳ETag給Web server,如果ETag匹配,則伺服器返回304代碼,從而節省了下載時間:
GET /i/yahoo.gif HTTP/1.1
登入後複製
If-Modified-Since: Tue, 12 Dec 2006 03:03:59 GMT
登入後複製
If-None-Match: "10c24bc-4ab-457e1c1f"
登入後複製
HTTP/1.1 304 Not Modified
登入後複製
ETags的問題在於它們是基於伺服器唯一性的某些屬性建構的,如Apache1.3和2.x,其格式是inode-size-timestamp,而在IIS5.0和6.0下,其格式是Filetimestamp :ChangeNumber。這樣同一個元素在不同的web server上,其ETag是不一樣的。這樣在多Web server的環境下,瀏覽器先從server1請求某個元素,後來再向server2驗證該元素,由於ETag不同,所以快取失效,必須重新下載。
因此,如果您未使用到ETags系統提供的靈活的驗證機制,最好刪除ETag。刪除ETag會減少http response及後續請求的HTTP頭的大小。微軟支援文章描述如何刪除ETags,而在Apache下,只要在設定檔中設定FileETag none即可。
我們的應用:
E網打盡:自訂ETag的生成策略,以盡量減少探頭規則的產生次數。由於不是採用伺服器預設的ETag,所以不存在該問題。
其他產品線:要注意了,這點大家都沒有關注過吧,趕快檢查一下Apache中的設定。
法則14. 快取Ajax
效能最佳化法則同樣適用於web 2.0應用。提高Ajax的性能最重要的方式是使得其response可緩存,就像「法則3增加Expires Header」討論的那樣。以下其他法則同樣適用於Ajax,當然法則3是最有效的方式:
法則4. 壓縮頁元素
法則9. 減少DNS查詢次數
法則10. 最小化腳本檔案
法則11. 避免重新導向
法則13. 配置ETags.
我們的應用:
更多情況下,我們倒不希望Ajax請求被緩存,此時為每個Ajax請求的url附加一個時間戳就可以了。