徐海蛟教學
Building a Shop with Sub-Second Page Loads: Lessons Learned
利用web緩存和NoSQL系統建立一個應對高訪問量的快速網上商店。
用戶滿意度和轉化率強相關(hard-wired),並直接影響利潤。
如何加速一個網站
有三大驅動力影響網頁應用的頁面加載時間:
後端處理:網絡服務器需要時間來從數據庫中加載數據和組裝網頁。
網絡延遲:每一個請求需要時間來從客戶端傳輸到服務器再返回(請求延時)。考慮到平均一個網頁需要超過100次請求才能完全加載,這就變得更重要了。
前端處理:前端設備需要時間來渲染頁面。
爲了加速我們的網站,讓我們逐個處理這三個瓶頸。
前端性能
前端性能的最重要因素是關鍵呈現(渲染)路徑(critical rendering path - CRP)。它描述了瀏覽器對用戶呈現頁面的5個必要步驟:
DOM:當瀏覽器解析 HTML時,它會逐漸生成一個HTML標籤的樹狀模型,名爲文檔對象模型(document object model - DOM)。它描述了頁面的內容。
html解析過程
html解析時間
CSSOM:一旦瀏覽器接收到了全部的CSS,CSS 字節會轉換爲字符,然後轉換爲符號和節點,最後鏈接進樹狀結構上,稱爲CSS對象模型,它包含樣式信息。
Render Tree:CSSOM 和 DOM 是獨立的數據結構,爲了將DOM和CSSOM組合起來,瀏覽器構造了一個渲染樹,它包含了頁面內容和要應用的樣式信息。
Layout:佈局步驟計算顯示在屏幕上的網頁內容的實際位置和大小。
Paint:最後一步就是將佈局信息逐像素點繪製到屏幕上。
單獨的每一步都是相當直接的,是步驟之間的依賴關係使得事情變得複雜,並限制了性能。DOM和CSSDOM的構建通常具有最大的性能影響。
下圖展示了關鍵呈現路徑以及用箭頭表示的等待依賴關係:在加載完CSS並構建完整的CSSOM之前沒有東西會顯示給客戶端。因此,CSS被稱爲渲染阻塞,即CSS 被視爲阻塞渲染的資源。
JavaScript(JS)更是雪上加霜,因爲它可以訪問和更改DOM和CSSOM。這意味,一旦一個腳本標籤在HTML中被發現,DOM構建將會暫停並向服務器請求該腳本。一旦腳本被加載它也不能被執行,必須等到所有的CSS被獲取並且CSSOM被構建完成。在CSSOM構建完成後,JS被執行,比如下面的例子,它會訪問並修改DOM以及CSSDOM。只有這之後,DOM的構建纔會繼續,頁面纔會被展示給客戶。因此,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(關鍵呈現路徑),有三個基本的概念:
減少關鍵資源數量:關鍵資源是那些頁面首次渲染所需要的資源(HTML,CSS,JS文件)。通過使用內聯CSS和JS來渲染網頁上無需滾動即可見的部分(稱爲Above the fold,工具),這些能被極大地減少。更多的JS和CSS應當被異步加載。不能異步加載的文件可以級聯成一個文件。
最小化字節數:CRP中加載的字節數可以通過刪減和壓縮CSS,JS和圖片來大大減少。
縮短關鍵呈現路徑長度:CRP長度是 爲了獲取所有關鍵資源從客服端到服務器的連續往返的最大數。減少關鍵資源以及最小化它們的大小(大文件需要多次往返獲取)都可以縮短它。進一步,將CSS包含在HTML的頭部,將JS放在HTML的底部也會有所助益,因爲JS的執行將會阻塞於CSS的獲取和CSSDOM的構建,無論如何也會阻塞DOM的構建。
性能測試(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優化。
網絡性能
當談到頁面加載時間時,網絡延遲是最重要的因素,它也是最難優化的。但是在我們進入優化之前,讓我們來看看一次瀏覽器初始請求的明細:
當我們在瀏覽器中輸入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的數據。
所以網站用很多小請求來加載許多資源,但是網絡帶寬會不斷增加。網絡物理結構的演進會拯救我們,對不對?嗯,這不是真的。。。
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)來保持緩存數據一致。
瀏覽器緩存(見上圖)中對重複頁面加載不能提供的請求僅僅是兩個對谷歌分析API的異步調用和一個對初始HTML(從CDN中獲取到)的請求。因此,重複頁面的加載幾乎是一瞬間。
後端性能
對於後端性能,我們需要考慮延遲和吞吐量。爲了獲得低延遲,我們需要最小化服務器的處理時間。爲了維持高吞吐量和應對負載峯值,我們需要採用橫向拓展(horizontally scalable)的架構。如果不考慮太多的細節——設計決策影響性能的空間是巨大的——這些就是最重要的組件和屬性了:
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上構建,它使用瞭如下的後端堆棧:
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萬)被配置成執行一個額外的支付過程。
我們發現在我們的支付實現中有幾個瓶頸,比如我們不得不從庫存的樂觀更新(用findAndModify實現)切換到MongoDB的部分更新操作(inc)。但是在這之後,我們的服務器能夠良好地處理負載,平均請求延遲爲5ms。
所有負載測試組合起來產生了約1000萬的請求,傳輸了460GB的數據,達到了99.8%的CDN緩存命中率。
總結
總之,良好的用戶體驗建立在三大支柱上:前端、網絡和後端的性能。
前端性能在我看來是最容易實現的,因爲已經有很好的工具和一堆容易遵循的最佳實踐。但是仍然有許多網站沒有遵循這些最佳實踐,根本不優化它們的前端。
網絡性能是頁面加載時間最重要的因素,也是最難優化的。緩存和CDN是最有效的優化方式,但是即使是對靜態內容也需要可觀的努力。
後端性能依賴於單服務器性能和你在機器之間分配工作的能力。橫向擴展特別難以實現,必須要在一開始就加以考慮。很多項目把可伸縮性和性能作爲事後的想法,當它們的業務增長時遇到了大麻煩。
文獻與工具建議
有許多關於網絡性能和可擴展系統的好書。Ilya Grigorik 的High Performance Browser Networking幾乎包含了一切你需要知道的關於網絡和瀏覽器性能的知識,而且它的持續更新版本是免費在線閱讀的!Martin Kleppmann 的Desining Data-Intensive Applications仍然是早期版本,但已躋身該領域內最好的書了。它涵蓋了大部分可拓展後端系統背後的基礎,非常詳細。 Lara Callender Hogan 的Designing for Performance全是關於構建具有良好用戶體驗的快速網站,包含了許多最佳實踐。
同時,網上也有許多值得考慮的優秀指導,教程和工具。從對初學者友好的Udacity課程Website Performance Optimization到谷歌的developer performance guide,以及許多分析工具如Google PageSpeed Insights, GTmetrix 和 WebPageTest。
未完
Newest Developments In Web Performance —— Web性能的最新發展
Accelerated Mobile Pages