天天看點

如何加速一個網站——web性能三方面[轉載]

徐海蛟教學

Building a Shop with Sub-Second Page Loads: Lessons Learned

利用web緩存和NoSQL系統建立一個應對高通路量的快速網上商店。

  • 使用者滿意度和轉化率強相關(hard-wired),并直接影響利潤。
    如何加速一個網站——web性能三方面[轉載]

如何加速一個網站

有三大驅動力影響網頁應用的頁面加載時間:

  1. 後端處理:網絡伺服器需要時間來從資料庫中加載資料群組裝網頁。
  2. 網絡延遲:每一個請求需要時間來從用戶端傳輸到伺服器再傳回(請求延時)。考慮到平均一個網頁需要超過100次請求才能完全加載,這就變得更重要了。
  3. 前端處理:前端裝置需要時間來渲染頁面。

為了加速我們的網站,讓我們逐個處理這三個瓶頸。

前端性能

  • 前端性能的最重要因素是關鍵呈現(渲染)路徑(critical rendering path - CRP)。它描述了浏覽器對使用者呈現頁面的5個必要步驟:
    如何加速一個網站——web性能三方面[轉載]
    • DOM:當浏覽器解析 HTML時,它會逐漸生成一個HTML标簽的樹狀模型,名為文檔對象模型(document object model - DOM)。它描述了頁面的内容。
      如何加速一個網站——web性能三方面[轉載]
      html解析過程
      如何加速一個網站——web性能三方面[轉載]
      html解析時間
    • CSSOM:一旦浏覽器接收到了全部的CSS,CSS 位元組會轉換為字元,然後轉換為符号和節點,最後連結進樹狀結構上,稱為CSS對象模型,它包含樣式資訊。
      如何加速一個網站——web性能三方面[轉載]
      參考: Google開發者--建構對象模型
    • Render Tree:CSSOM 和 DOM 是獨立的資料結構,為了将DOM和CSSOM組合起來,浏覽器構造了一個渲染樹,它包含了頁面内容和要應用的樣式資訊。
      如何加速一個網站——web性能三方面[轉載]
    • Layout:布局步驟計算顯示在螢幕上的網頁内容的實際位置和大小。
      如何加速一個網站——web性能三方面[轉載]
    • Paint:最後一步就是将布局資訊逐像素點繪制到螢幕上。
      如何加速一個網站——web性能三方面[轉載]
      參考: Google開發者--渲染樹建構、布局及繪制
  • 單獨的每一步都是相當直接的,是步驟之間的依賴關系使得事情變得複雜,并限制了性能。DOM和CSSDOM的建構通常具有最大的性能影響。

    下圖展示了關鍵呈現路徑以及用箭頭表示的等待依賴關系:

    如何加速一個網站——web性能三方面[轉載]
  • 在加載完CSS并建構完整的CSSOM之前沒有東西會顯示給用戶端。是以,CSS被稱為渲染阻塞,即CSS 被視為阻塞渲染的資源。
    參考: Google開發者--阻塞渲染的 CSS
  • JavaScript(JS)更是雪上加霜,因為它可以通路和更改DOM和CSSOM。這意味,一旦一個腳本标簽在HTML中被發現,DOM建構将會暫停并向伺服器請求該腳本。一旦腳本被加載它也不能被執行,必須等到所有的CSS被擷取并且CSSOM被建構完成。在CSSOM建構完成後,JS被執行,比如下面的例子,它會通路并修改DOM以及CSSDOM。隻有這之後,DOM的建構才會繼續,頁面才會被展示給客戶。是以,JavaScript是所謂的解析器阻塞。
    參考: Google開發者--使用 JavaScript 添加互動
Example of JavaScript accessing CSSOM and changing DOM:

<script>
   ...
   var old = elem.style.width;
   elem.style.width = "50px";
   document.write("alter DOM");
   ...
</script>
           
  • 此外,JS甚至可以比這更有害。比如類似這樣的jQuery插件,它将通路計算好的HTML元素的布局資訊,然後開始一次又一次修改CSSOM直到它希望的布局。其結果是,浏覽器不得不一次又一次重複JS的執行、渲染樹建構和布局,使用者隻有等待這些操作完成才能真正看到顯示。
  • 為了優化CRP(關鍵呈現路徑),有三個基本的概念:
    1. 減少關鍵資源數量:關鍵資源是那些頁面首次渲染所需要的資源(HTML,CSS,JS檔案)。通過使用内聯CSS和JS來渲染網頁上無需滾動即可見的部分(稱為Above the fold,工具),這些能被極大地減少。更多的JS和CSS應當被異步加載。不能異步加載的檔案可以級聯成一個檔案。
    2. 最小化位元組數:CRP中加載的位元組數可以通過删減和壓縮CSS,JS和圖檔來大大減少。
    3. 縮短關鍵呈現路徑長度:CRP長度是 為了擷取所有關鍵資源從客服端到伺服器的連續往返的最大數。減少關鍵資源以及最小化它們的大小(大檔案需要多次往返擷取)都可以縮短它。進一步,将CSS包含在HTML的頭部,将JS放在HTML的底部也會有所助益,因為JS的執行将會阻塞于CSS的擷取和CSSDOM的建構,無論如何也會阻塞DOM的建構。
  • 此外,浏覽器緩存也是非常有效的,應當始終使用它。它适用于全部三種類型的優化,因為緩存的資源不用再從伺服器加載。
  • CRP優化的整個主題是相當複雜的,尤其是内聯,串聯和異步加載可以毀掉代碼的可讀性。值得慶幸的是有很多偉大的工具樂意為你做這些優化,它們能被內建進你的建構和部署鍊。你應該好好地看看以下工具:
    • 性能測試(Profiling):GTmetrix用來測量頁面的速度, webpagetest用來分析資源,Google的 PageSpeed Insights可以生成針對你的網頁如何優化CRP的具體提示。
    • 内聯和優化(Inlining and optimization):Critical可以很好地自動化内聯第一眼可見區(above the fold)的CSS并異步加載剩餘的。processhtml串聯你的資源。PostCSS進一步優化CSS。
    • 删減和壓縮(Minification and compression):tiny png 用來壓縮圖檔,UglifyJs和 cssmin用來删減,或者用Google Closure做JS優化。
  • 使用這些工具并做一點必要的工作,你可以達到良好的前端性能。
    如何加速一個網站——web性能三方面[轉載]
    Google分析Insights

網絡性能

  • 當談到頁面加載時間時,網絡延遲是最重要的因素,它也是最難優化的。但是在我們進入優化之前,讓我們來看看一次浏覽器初始請求的明細:
    如何加速一個網站——web性能三方面[轉載]
  • 當我們在浏覽器中輸入https://www.thinks.com/然後回車,浏覽器開始一次DNS查詢來找到域名相應的IP位址。每一個獨立的域名都需要一次查詢。
  • 收到IP位址後,浏覽器發起與伺服器的TCP連接配接。TCP握手需要2次往返(TCP快速打開隻需要1次)。使用安全的SSL連接配接,TLS握手需要額外的2次往返(TLS False Start 或 Session Resumption隻要1次)
  • 初始連接配接之後,浏覽器發送真正的請求并等待資料傳回。這個第一位元組接收時間(time to first byte)主要受到兩方面影響:一是用戶端和伺服器的距離,二是伺服器渲染頁面所需要的時間(包括session查找,資料庫查詢,模闆渲染等等)。
  • 最後一步就是下載下傳資源(這個例子中是HTML),潛在地需要多次往返。特别是新的連接配接,通常需要許多往返,因為最初擁塞視窗很小。這意味着TCP并沒有在一開始就使用全部帶寬而是随時間增加帶寬使用(TCP congestion control)。傳輸速度受慢啟動算法控制,該算法每次往返後增大一倍擁塞視窗直到丢包情況發生。是以,在移動裝置和Wifi網絡中丢包會産生較大的性能影響。
  • 另一個需要記在心上的是:在HTTP/1.1下,你隻能得到6個并行的連接配接(如果浏覽器遵循原始标準隻有2個)。是以,你最多隻能并行請求6個資源。
  • 為了得到一個網絡性能對于頁面速度的重要性的直覺認識,httparchive 網站上有許多資料。例如,一般的網站要在超過100個請求中加載約2.5MB的資料。
    如何加速一個網站——web性能三方面[轉載]
  • 是以網站用很多小請求來加載許多資源,但是網絡帶寬會不斷增加。網絡實體結構的演進會拯救我們,對不對?嗯,這不是真的。。。
    如何加速一個網站——web性能三方面[轉載]
    From  High Performance Browser Networking by Ilya Grigorik
  • 事實證明,超過5Mbps再增加帶寬對頁面加載時間并沒有多大影響。但是縮短個體請求延時壓低網絡加載時間。這意味着帶寬成倍增長帶給你相同的加載時間,而删減一半的延遲會讓你的加載時間減半。
  • 是以,如果延遲是網絡性能的決定性因素,我們能對它做些什麼呢?
    • 持久連接配接(Persistent connections)是必備的。如果你的伺服器在每次處理請求後關閉連接配接,浏覽器不得不一次又一次地重新執行握手和TCP慢啟動,沒什麼比這更糟的了。
    • 避免重定向(Avoid redirects)避免可能的重定向因為它們會減慢你的初始頁面加載速度。例如始終連結完整的url(www.thinks.com之于thinks.com)。
    • 如果可能的話使用HTTP/2。它配備了伺服器端推送(server push)來對單個請求傳輸多個資源,報頭壓縮(header compression)來壓低請求和響應的大小 以及 請求流水線(pipelining )和複用(multiplexing )技術在單個連接配接中發送任意并行請求。使用伺服器推送,舉例說就是,你的伺服器可以在傳輸html之後緊接着就推送網站所需的CSS、JS而不需要等待真正的請求。
    • 設定明确的緩存頭(caching headers ),為你的靜态資源(CSS,JS,靜态圖像如logo)。這樣你可以告訴浏覽器緩存這些資源多長時間,何時再驗證。緩存潛在地幫你節省了許多往返和位元組下載下傳。如果沒有明确的頭标簽設定,浏覽器會做啟發式緩存(heuristic caching),這會比沒有好但遠不是最佳。
    • 使用内容分發網絡(Content Delivery Network - CDN)來緩存圖像,CSS,JS和HTML。這些分布式緩存網絡能顯著地減少你與使用者的距離,進而快速傳遞資源。 它們還加速了你的初始連接配接,因為可以使用一個使用者附近的CDN節點來做TCP和TLS握手。
    • 考慮使用一個小的初始頁面、異步加載額外部分的方式,來做單頁應用(Single-Page App)。這樣你可以使用可緩存的HTML模闆,在使用者浏覽過程中用小請求來加載動态資料并且隻更新頁面的一部分。
  • 總的來說,當涉及網絡性能時,有幾個該做和不該做的注意事項,但是限制因素總是往返數和實體網絡延遲的組合。征服這個限制的唯一有效辦法是讓資料靠客戶更近。Web緩存正是這麼做的,但它隻适用于靜态資源。
  • 對于Thinks網站,我們遵循了上述方針,使用了Fastly CDN以及激進的浏覽器緩存方案——即使對動态資料也是,并使用了一種新型的布隆過濾算法(Bloom Filter algorithm)來保持緩存資料一緻。
    如何加速一個網站——web性能三方面[轉載]
    浏覽器緩存(見上圖)中對重複頁面加載不能提供的請求僅僅是兩個對谷歌分析API的異步調用和一個對初始HTML(從CDN中擷取到)的請求。是以,重複頁面的加載幾乎是一瞬間。

後端性能

  • 對于後端性能,我們需要考慮延遲和吞吐量。為了獲得低延遲,我們需要最小化伺服器的處理時間。為了維持高吞吐量和應對負載峰值,我們需要采用橫向拓展(horizontally scalable)的架構。如果不考慮太多的細節——設計決策影響性能的空間是巨大的——這些就是最重要的元件和屬性了:
    如何加速一個網站——web性能三方面[轉載]
    Components of a scalable backend stack: load balancers, stateless application servers, distributed databases
    • 首先,你需要負載均衡(load balancing)(例如亞馬遜的ELB或DNS負載均衡)來配置設定傳入的請求到你的多個應用伺服器。它也應該實作自動伸縮(automatic scaling )來在需要時産生額外的應用伺服器,以及實作故障轉移(failover )來更換壞掉的伺服器并将請求重新路由到健康的伺服器。
    • 應用伺服器應盡量減少共享狀态(minimize shared state )來讓協同最小,并使用無狀态的會話處理(stateless session handling)達到自由的負載平衡。此外,伺服器應當保證代碼和IO的效率來最小化伺服器處理時間。
    • 資料庫也需要承受負載峰值,實作盡可能少的處理時間。同時,它們也需要對模型建立和資料查詢具有足夠的表現力。目前有大量的可拓展資料庫(特别是NoSQL),每個都有自己的一套權衡方案。更多細節可以看我們關于這一主題的調查和決策指導:NoSQL Databases: a Survey and Decision Guidance。
  • Thinks 網上商店在Baqend上建構,它使用了如下的後端堆棧:
    如何加速一個網站——web性能三方面[轉載]

    Baqend’s backend stack: MongoDB as the main database, stateless application servers, HTTP caching hierarchy, REST and the JS SDK for the web frontend

    主要使用的資料庫是MongoDB。為了維持我們即将到期的布隆過濾器(用于浏覽器緩存),我們使用Redis因為它的高寫入吞吐量。無狀态應用伺服器(Orestes Servers)提供了後端特性的接口(檔案托管,資料存儲,實時查詢,推送通知,通路控制等)并處理動态資料的緩存一緻性。它們從CDN得到請求,CDN也充當一個負載均衡器。網站前端使用一個基于REST API的JS SDK來通路後端,後端自動利用了完整的HTTP緩存層次結構(HTTP caching hierarchy)來加速請求,并保持緩存資料達到最新。

負載測試

  • 為了測試Thinks網上商店在高負載下的表現,我們用了2個位于法蘭克福的t2.medium型号的AWS執行個體上的應用伺服器。負載測試的建構使用了JMeter,測試執行在IBM soft layer上的20台機器來模拟在15分鐘内的20萬使用者通路以及網站浏覽。20%的使用者(4萬)被配置成執行一個額外的支付過程。
    如何加速一個網站——web性能三方面[轉載]
  • 我們發現在我們的支付實作中有幾個瓶頸,比如我們不得不從庫存的樂觀更新(用findAndModify實作)切換到MongoDB的部分更新操作(inc)。但是在這之後,我們的伺服器能夠良好地處理負載,平均請求延遲為5ms。
    如何加速一個網站——web性能三方面[轉載]
  • 所有負載測試組合起來産生了約1000萬的請求,傳輸了460GB的資料,達到了99.8%的CDN緩存命中率。
    如何加速一個網站——web性能三方面[轉載]

總結

  • 總之,良好的使用者體驗建立在三大支柱上:前端、網絡和後端的性能。
    如何加速一個網站——web性能三方面[轉載]
  • 前端性能在我看來是最容易實作的,因為已經有很好的工具和一堆容易遵循的最佳實踐。但是仍然有許多網站沒有遵循這些最佳實踐,根本不優化它們的前端。
  • 網絡性能是頁面加載時間最重要的因素,也是最難優化的。緩存和CDN是最有效的優化方式,但是即使是對靜态内容也需要可觀的努力。
  • 後端性能依賴于單伺服器性能和你在機器之間配置設定工作的能力。橫向擴充特别難以實作,必須要在一開始就加以考慮。很多項目把可伸縮性和性能作為事後的想法,當它們的業務增長時遇到了大麻煩。

文獻與工具建議

如何加速一個網站——web性能三方面[轉載]
  • 有許多關于網絡性能和可擴充系統的好書。Ilya Grigorik 的High Performance Browser Networking幾乎包含了一切你需要知道的關于網絡和浏覽器性能的知識,而且它的持續更新版本是免費線上閱讀的!Martin Kleppmann 的Desining Data-Intensive Applications仍然是早期版本,但已跻身該領域内最好的書了。它涵蓋了大部分可拓展後端系統背後的基礎,非常詳細。 Lara Callender Hogan 的Designing for Performance全是關于建構具有良好使用者體驗的快速網站,包含了許多最佳實踐。
如何加速一個網站——web性能三方面[轉載]
  • 同時,網上也有許多值得考慮的優秀指導,教程和工具。從對初學者友好的Udacity課程Website Performance Optimization到谷歌的developer performance guide,以及許多分析工具如Google PageSpeed Insights, GTmetrix 和 WebPageTest。

未完

  • Newest Developments In Web Performance —— Web性能的最新發展

    Accelerated Mobile Pages

其它

The Website Obesity Crisis

Chrome DevTools Overview