- 原文地址:Building a Shop with Sub-Second Page Loads: Lessons Learned
- 原文做者:Erik Witt
- 譯文出自:掘金翻譯計劃
- 譯者:luoyaqifei
- 校對者:Romeo0906,L9m
這裏是 咱們 充分利用對於網絡緩存和 NoSQL 系統的研究,作出一個能夠容納幾十萬經過電視宣傳慕名而來的訪問者的 網上商城 的故事,以及咱們從中學到的一切。php
"Shark Tank"(美國),"Dragons’ Den"(英國)或" Die Höhle der Löwen(DHDL)"(德國)等電視節目爲年輕初創公司供了一次在衆多觀衆前向商業大亨推銷本身產品的機會。然而,主要的好處每每不在於評審團提供的戰略投資——只有少數交易會完成——而是在電視節目播放期間引起的關注:即便是幾分鐘的直播也能給網站帶來幾十萬的新用戶,同時可以提升幾周、幾個月甚至永久性的網站基本活躍水平。也就是說,若是網站能夠抓住初始負載尖峯,而且不拒絕用戶請求……css
網上商城的盈利壓力特別大,由於他們不僅是消遣項目(諸如博客),但一般因爲創始人自己有大量投資支持,必須轉化爲利潤。很明顯,對於商業業務來講,最壞的狀況是網站過載,在此期間服務器不得不丟掉部分用戶請求甚至可能徹底崩潰。這並不像你想象的那樣罕見:在 DHDL 的這一季,大約有一半的網上商店在直播現場就沒法鏈接了。而且,保持在線只有一半的租金,由於用戶滿意度是強制鏈接到轉化率,從而直接轉化爲產生的收入的。html
Source前端
關於頁面加載時間對客戶滿意度和轉換率的影響,有不少 研究 支持這種說法。例如,Aberdeen Group 發現,額外延遲的 1 秒會致使頁面瀏覽量減小 11%,轉化次數損失 7%。 但你也能夠詢問 Google 或 Amazon,他們會告訴你一樣的說法。jquery
爲初創公司 Thinks 搭建的網上商城參與了 DHDL,並在 9 月 6 日播出。咱們面臨着一個挑戰,搭建一個可以承受數十萬訪客量的網上商店,而且加載時間穩定在 1 秒之內。如下都是咱們在這個過程當中以及從近些年對數據庫和網絡的性能研究中學到的。git
在現有的 web 應用技術中有三個影響頁面加載時間的主要緣由,展現以下:github
爲了讓咱們的網店加速,讓咱們一一解決這三個瓶頸。web
影響前端性能最重要的因素是關鍵呈現路徑(CRP),它描述了在瀏覽器中向用戶顯示頁面所需的 5 個必要步驟,以下所示。算法
關鍵呈現路徑的步驟:sql
單個步驟是至關簡單的,使事情變得困難並限制性能的是這些步驟之間的依賴。DOM 和 CSSOM 的構造一般具備最大的性能影響。
這個圖表顯示了關鍵呈現路徑的步驟,裏面包括等待依賴,如箭頭所示。
關係呈現路徑中重要的依賴
在加載 CSS 和構造完整的 CSSOM 以前,什麼都不能顯示給客戶端。所以 CSS 被稱爲是阻塞渲染的。
JavaScript(JS)更糟糕,由於它能夠訪問和更改 DOM 和 CSSOM。 這意味着一旦發現 HTML 中的腳本標記,DOM 構造就會被暫停,並從服務器請求腳本。一旦腳本被加載,只有在全部 CSS 被提取和 CSSOM 被構造之後,它才能被執行。在 CSSOM 構建以後 JS 被執行,在下面的例子中,它能夠訪問和改變 DOM 以及 CSSOM。只有這樣以後,DOM的構造才能進行,而且頁面才能顯示給客戶端。所以 JavaScript 被稱爲是阻塞解析的。
JavaScript 訪問 CSSOM 和更改 DOM 的示例:
<script> ... var old = elem.style.width; elem.style.width = "50px"; document.write("alter DOM"); ... </script>
JS 甚至會影響更惡劣。例如 jQuery 插件 訪問計算後的 HTML 元素的佈局信息,而後開始一次又一次地改變 CSSOM,直到實現了所需的佈局。所以,在用戶將看到白色屏幕之外的任何東西以前,瀏覽器必須一次又一次地重複地執行 JS、構造渲染樹和佈局。
有三個優化 CRP 的 基本概念:
此外,瀏覽器緩存 是很是有效的,應該在全部的項目中加以使用。它對於這三個優化項都很合適,由於緩存的資源沒必要先從服務器加載。
CRP 優化的整個主題是至關複雜的,特別是內聯、級聯和異步加載,它們可能會破壞代碼的可重用性。幸運的是,有不少強大的工具,能夠爲你作好這些優化,這些工具能夠被集成到你的構建和部署鏈裏。你的確應該地看看下面的工具……
有了這些工具只需很小的工做量,你就能夠打造一個前端性能極好的網站。這裏是 Thinks 商城第一次訪問時的頁面速度測試:
thinks.com 的 Google 網頁速度分數
有趣的是,PageSpeed Insights 內部惟一的抱怨是,Google 分析的腳本緩存生命週期過短。因此 Google 基本上在抱怨它本身。
來自加拿大(GTmetrix)的第一次頁面加載,服務器託管在法蘭克福(Frankfurt)
網絡延遲是頁面加載時間最重要的因素,它也是最難優化的。但在咱們進行優化以前,讓咱們看一下對初始的瀏覽器請求的劃分:
當咱們在瀏覽器中輸入 https://www.thinks.com/ 並按下回車鍵時,瀏覽器開始使用 DNS 查找來識別與域相關聯的 IP 地址,這種查找必須對每一個單獨的域進行。
使用接收到的 IP 地址,瀏覽器初始化與服務器的 TCP 鏈接。TCP 握手須要 2 次往返(1 次是 TCP 快速打開)。使用安全的 SSL 鏈接,TLS 握手須要額外的 2 次往返(1 次是 TLS False Start 或 Session Resumption)。
在初始鏈接以後,瀏覽器發送實際請求並等待數據進入。第一個字節到達的時間主要取決於客戶端和服務器之間的距離,包括服務器渲染頁面所需的時間(包括會話查找、數據庫查詢和模板渲染等)。
最後一步是在可能的屢次往返中下載資源(在這種狀況下指的是 HTML )。新鏈接尤爲一般須要不少往返,由於初始擁塞窗口很小。這意味着 TCP 不是從一開始就使用全帶寬,而是隨着時間的推移而增長帶寬(參見 TCP擁塞控制。下載速度受到慢啓動算法的支配,該算法在每次往返的擁塞窗口中將報文段數量加倍,直到丟包發生。在移動網絡和 Wifi 網絡上丟失數據包所以具備很大的性能影響。
另外一件要記住的事是:使用 HTTP/1.1,你只能獲得 6 個並行鏈接(若是瀏覽器仍然遵循原始標準,則鏈接數爲 2)。所以,你最多隻能請求 6 個資源並行。
爲了對網絡性能對於頁面速度的重要性有一個直觀的認識,你能夠查看 httparchive ,上面有不少統計數據。例如,網站平均在 100 多個請求中加載大約 2.5 MB的數據。
因此網站發出了不少小的請求來加載不少資源,但網絡帶寬一直在增長。物理網絡的演進將拯救咱們,對吧?嗯,其實並非……
來自 High Performance Browser Networking,做者爲 Ilya Grigorik
事實證實,將帶寬增長到 5 Mbps 以上並不真的影響頁面加載時間。但減小單個請求的延遲會下降網頁加載時間。這意味着帶寬加倍帶來的是相同的加載時間,而減小一半的延遲將給你一半的加載時間。
所以,若是延遲是網絡性能的決定因素,咱們能夠在這上面作些什麼呢?
爲你的靜態資源(CSS,JS,靜態圖像如 logo)設置顯式的緩存頭。這樣,你能夠告訴瀏覽器須要將這些資源緩存多長時間以及什麼時候從新驗證。緩存能夠節省大量的往返和須要下載的字節。若是沒有設置明確的緩存頭,瀏覽器會作 啓發式緩存,這比不緩存好,但遠不是最佳。
使用內容分發網絡(CDN)來緩存圖像、CSS、JS 和 HTML。這些分佈式緩存網絡能夠顯著地減小與用戶的距離,從而更快地提供資源。它們還加速了你的初始鏈接,由於你與附近的 CDN 節點進行 TCP 和 TLS 握手,而這些節點會依次創建熱的和持久的後端鏈接。
總而言之,當涉及到網絡性能時,有一些要作的(do) 和不要作的(don't),但限制因素老是往返次數與物理網絡延遲的結合。克服這種限制的惟一有效方法是使數據更接近客戶端。最早進的網絡緩存狀態的確如此,但這僅適用於靜態資源。
對於 Thinks,咱們遵循上述準則,使用 Fastly CDN 和主動的瀏覽器緩存,甚至對動態數據使用一種新的 布隆過濾器算法(Bloom Filter algorithm) 來使得緩存數據保持一致。
www.thinks.com 重複加載,來顯示瀏覽器緩存覆蓋率
對於重複網頁加載的請求,瀏覽器緩存沒有提供的內容(參見上圖)包括:對 Google 分析的 API 的兩個異步調用,以及從 CDN 處獲取的初始 HTML 請求。所以,對於重複的網頁加載,頁面可以作到當即加載。
對於後端性能,咱們須要同時考慮延遲和吞吐量。爲了實現低延遲,咱們須要將服務器的處理時間最小化。爲了保持高吞吐量和應對負載尖峯,咱們須要採用一種水平可擴展的架構。咱們不會談到太多細節,由於設計決策對性能的影響空間是巨大的,這些是須要去尋找的最重要的組件和屬性:
可擴展的後端技術棧組件:負載均衡器,無狀態應用服務器,分佈式數據庫
首先,你須要負載均衡(例如 Amazon ELB 或 DNS 負載均衡)將傳入的請求分配給你的一個應用服務器。它還應該實現自動調節功能,在須要時生成其餘應用服務器,以及故障轉移功能,以替換損壞的服務器並將請求從新路由到正常服務器。
應用服務器應將共享狀態最小化,從而保持協調最少,並使用無狀態會話處理來啓用自由的負載均衡。此外,服務器應該有高效的代碼和 IO,使得服務器處理時間最小。
數據庫須要承受負載尖峯,並儘量減小處理時間。同時,它們須要具備足夠的表達性,以根據須要建模和查詢數據。有大量的可擴展數據庫(尤爲是 NoSQL),每一個都有本身的 trade-off。詳細信息請參考咱們關於該主題的調查和決策指南:
NoSQL 數據庫:一份調查和決策指南
與咱們在漢堡大學的同事一塊兒,咱們是:Felix Gessert, Wolfram Wingerath, Steffen…medium.baqend.com
Thinks 網上商城搭建在 Baqend 上,使用了以下的後端技術棧:
Baqend的後端技術棧:MongoDB 做爲主數據庫,無狀態應用服務器,HTTP 緩存層次結構,REST 和 web 前端的 JS SDK
用於 Thinks 的主數據庫是 MongoDB。爲了維護咱們將要到期的布隆過濾器(用於瀏覽器緩存),咱們使用 Redis ,由於它的高寫入吞吐量。無狀態應用程服務器(Orestes Servers)爲後端功能提供接口(文件託管,數據存儲,實時查詢,推送通知,訪問控制等),並處理動態數據的緩存一致性。它們從 CDN 拿到請求,CDN 也充當負載均衡器。網站前端使用基於 REST API 的 JS SDK 來訪問後端,後端自動利用完整的 HTTP 緩存層次結構來讓請求加速並保持緩存數據時刻最新。
爲了在高負載下測試 Thinks 網上商城,咱們在法蘭克福的 t2.medium AWS 實例上使用 2 個應用服務器來進行負載測試。MongoDB 在兩個 t2.large 實例上運行。使用 JMeter 構建負載測試並在 IBM soft layer 上的 20 個機器上運行,以模擬在 15分鐘內,200,000 個用戶同時訪問和瀏覽網站。20% 的用戶(40,000)被配置爲執行額外的付款流程。
網上商城的負載測試設置
咱們在支付實現中發現了一些瓶頸,例如,咱們必須從庫存的積極更新(使用 findAndModify實現)切換到 MongoDB 的部分更新操做(inc)。可是在這以後,服務器處理的負載只是精細地達到了平均請求延遲 5 ms。
JMeter 在負載測試期間輸出:在 12 分鐘內有 680 萬個請求,平均延遲 5 ms
全部的負載測試組合生成了大約 1000 萬個請求,傳輸了 460 GB的數據,伴隨着 99.8% 的 CDN 緩存命中率。
負載測試後的儀表板概述
總之,良好的用戶體驗取決於三個支柱:前端,網絡和後端的性能。
前端性能是咱們認爲最容易實現的,由於已經有不少工具和一些容易遵循的最佳實踐。但仍然有不少網站不遵循這些最佳實踐,徹底沒有優化過它們的前端。
網絡性能對於頁面加載時間來講,是最重要的因素,也是最難優化的。緩存和 CDN 是最有效的優化方法,但即便對於靜態內容也要付出至關大的努力。
後端性能取決於單服務器性能和跨機器去分發工做的能力。水平可擴展性特別難以實現,必須從一開始就考慮。許多項目將可擴展性和性能做爲過後處理,然而在它們的業務增加時會陷入大麻煩。
有不少關於 web 性能和可擴展系統設計的書:由 Ilya Grigorik 所寫的 高性能瀏覽器網絡 包含了幾乎全部你須要瞭解的網絡和瀏覽器性能知識,而且目前不斷更新的版本能夠免費在線閱讀哦!Martin Kleppmann 寫的 設計數據密集型應用 仍處於前期發佈狀態,但已是其領域最好的書之一,它涵蓋了可擴展後端系統背後的大部分基礎知識,並擁有至關多的細節。設計性能 由Lara Callender Hogan 寫成,圍繞着構建快速的、具備良好的用戶體驗的網站,涵蓋了不少最佳實踐。
還有一些很棒的在線指南、教程和工具能夠考慮:從初學者友好的 Udacity 課程 網站性能優化、Google 的 開發者性能指南 到相似於 Google PageSpeed Insights、GTmetrix 和 WebPageTest 這樣的優化工具。
移動頁面加速
Google 正在經過諸如 PageSpeed Insights、開發人員指南 等網站性能項目來提升你們對於網站性能的意識,並將網頁速度做爲其 網頁排名 的主要因素。
在 Google 搜索中用來提升網頁速度、加強用戶體驗的最新概念是 移動網頁加速(AMP)。其目的是讓新聞文章、產品頁面和其它搜索內容當即從 Google 搜索加載。爲此,這些頁面必須構建爲 AMP。
一個 AMP 頁面的示例
AMP 主要作兩件事:
構建爲 AMP 的網站使用精簡版本的 HTML,並使用 JS 加載器來快速渲染,並異步加載儘量多的資源。
Google 將網站緩存在 Google CDN 中,並經過 HTTP/2 分發。
第一件事從本質上意味着 AMP 以一種方式限制了你的 HTML、JS 和 CSS,這種方式構建的網頁有一個優化的關鍵呈現路徑,能夠很容易地被 Google 爬取。 AMP 強制 幾個限制,例如全部 CSS 必須內聯,全部 JS 必須是異步的,頁面上的全部內容必須具備靜態大小(以防止重繪)。 雖然你能夠經過堅持以前的 web 性能最佳實踐,在沒有這些限制的狀況下,實現相同的結果,但 AMP 多是很好的 trade-off ,可以爲很是簡單的網站提供幫助。
第二件事意味着,Google 抓取你的網站,而後將其緩存在 Google CDN 中,以便快速分發。網站內容會在爬蟲從新索引你的網站後更新。CDN 還遵循服務器設置的靜態 TTL,但至少執行 微緩存:資源至少在一分鐘內被視爲最新的,並在用戶請求進入時在後臺更新。所以 AMP 最適用於內容大可能是靜態的用戶案例。這種適用於人爲編輯修改的新聞網站或者其餘出版物的狀況。
Google 的另外一種作法是 漸進式 web 應用(PWA)。其想法是在瀏覽器中使用 服務工做者(service worker) 來緩存網站的靜態部分。所以,這些部分對於重複視圖會當即加載,並可離線使用。動態部分仍從服務器端加載。
app shell(單頁應用程序邏輯)能夠在後臺從新驗證。若是標識了對應用 shell 的更新,則會提示用戶,要求他更新頁面。例如,Gmail 收件箱 就實現了這個。
可是,寫出緩存靜態資源並進行從新驗證的服務工做者(service worker)代碼,對於每一個網站來講,都須要付出至關大的努力。此外,只有 Chrome 和 Firefox 充分地支持了服務工做者(service worker)。
全部緩存方法遇到的問題是它們不能處理動態內容。這只是因爲 HTTP 緩存的工做機制致使的。有兩種類型的緩存:基於失效的緩存(如轉發代理緩存和 CDN)和基於到期的緩存(如 ISP 緩存、機構代理和瀏覽器緩存)。基於失效的緩存能夠從服務器端主動失效,基於到期的高速緩存只能從客戶端從新驗證。
使用基於到期的緩存時,棘手的事情是,你必須在首次從服務器拿到數據時指定緩存生命週期(TTL)。以後,你沒有任何機會將緩存數據刪除。它將由瀏覽器緩存提供到 TTL 到期的時刻。對於靜態資源,這不是一件複雜的事情,由於它們一般只會在你部署 web 應用程序的新版本時發生變化。所以,你可使用 gulp-rev-all 和 grunt-filerev 等很酷的工具)對 assets 進行散列。
可是,可是你該如何處理運行時的應用數據加載和修改呢?更改用戶我的資料、更新帖子或添加新評論彷佛不可能與瀏覽器緩存結合使用,由於你沒法預估此類更新未來什麼時候會發生。所以,緩存只能被禁用或使用很是小的 TTL。
由另外一個客戶端更新時,緩存動態數據如何過期的示例
在 Baqend,咱們已經研究並開發了一種方法,在實際獲取以前,檢查客戶端中 URL 的陳舊度。在每一個用戶會話開始時,咱們獲取一個很是小的數據結構,稱爲布隆過濾器(Bloom Filter),它是全部過期資源集合的高度壓縮表示。經過查看布隆過濾器,客戶端能夠檢查資源是否過期(包含在布隆過濾器中)或者是不是全新的。對於潛在的過期資源,咱們繞過瀏覽器緩存並從 CDN 獲取內容。在其餘的全部狀況下,咱們直接用瀏覽器緩存提供內容。使用瀏覽器緩存能夠節省網絡流量和帶寬,而且是很快的。
此外,咱們確保 CDN(以及其它基於失效的緩存,如 Varnish)始終包含最新的數據,只要它們過期就當即清除資源。
Baqend 如何確保緩存動態數據的新鮮度示例
布隆過濾器(Bloom filter) 是具備可調誤報率的機率數據結構,這意味着集合能夠用來表示對從未添加的對象的遏制,但永遠不會刪除實際條目。換句話說,咱們可能偶爾會從新驗證新資源,可是咱們永遠不會提供過時數據。注意,誤報率很是低,這使得咱們可以讓集合很是小。例如,咱們只須要 11 Kbyte 來存儲 20,000 個不一樣的更新。
Baqend 在服務器端有不少流處理(查詢匹配檢測)、機器學習(最佳 TTL 估計)和分佈式協調(可擴展的布隆過濾器維護)的工做。若是你對這些細節感興趣,看看這篇 文章 或 這些幻燈片 來深刻研究。
這一切都歸結爲這一點。
使用 Baqend 的緩存基礎設施可使哪一種頁面速度獲得提升?
爲了展現使用 Baqend 的好處,咱們在後端即服務(BaaS)領域中的每一個領先競爭對手上構建了一個很是簡單的新聞應用,並觀測了來自世界各地不一樣位置的頁面加載時間。以下所示,Baqend 持續加載低於 1 秒,比平均速度快 6.8 倍。即便當全部客戶端來自服務器所在的同一位置時,因爲有瀏覽器緩存,Baqend 也是 150% 倍速度。
簡單新聞應用的平均加載時間比較
咱們將此比較做爲一個 動手的 web 應用 來比較 BaaS 競爭。
動手比較 的截圖
但這固然是一個測試場景,而不是一個具備真正用戶的 web 應用。 因此讓咱們回到 Thinks 網上商城來看一個真實世界的例子。
當 DHDL("Shark Tank"的德國版)在 9 月 6 日播出時,有 270 萬觀衆,咱們坐在電視和咱們的 Google 分析屏幕前,爲 Thinks 創始人提出他們的產品而激動。
從他們開始演示起,網上商的併發用戶數量迅速增長到大約 10,000,但真正的巔峯發生在廣告休息時,當時忽然有超過45,000 的併發用戶來參觀該店購買 Towell+:
Google 分析觀測在商業廣告時間以前開始。
Thinks 在電視播放的 30 分鐘裏,咱們獲得了 340 萬的請求,300,000 位遊客,高達 50,000 位的併發訪問遊客和高達每秒 20,000 個請求,全部這一切實現了在 CDN 級別的 98.5% 的緩存命中率,和平均爲 3% 的服務器 CPU 負載
所以,頁面加載時間爲低於 1 秒,整個時間實現了 7.8% 的極大的轉化率。
若是咱們看看在同一集 DHDL 中展現的其餘商城,咱們會看到其中四個 徹底崩潰了,剩下的商城只利用了極少的性能優化。
可用性概述和商城的 Google 頁面速度得分,在 DHDL 上,於 9 月 6 日展現。
咱們已經看到了在設計快速和可擴展的網站時須要克服的瓶頸:咱們必須掌握關鍵呈現路徑,理解網絡限制、緩存的重要性和具備水平可擴展性的後端設計。
咱們已經看到了不少用來解決單個問題的工具,以及移動加速頁面(AMP)和漸進式 web 應用(PWA),這些採起了更全面的作法。可是,緩存動態數據的問題仍然存在。
Baqend 的作法是減小 web 開發,將構建主要放在前端,經過 JS SDK 使用 Baqend 徹底託管的雲服務上的後端功能,包括數據和文件存儲、(實時)查詢、推送通知、用戶管理和 OAuth 以及訪問控制。該平臺經過使用完整的 HTTP 緩存層次結構自動加速全部請求,並確保可用性和可擴展性。
繼續前往免費試用 www.baqend.com.
PS:文章不錯,忍不住轉載了,轉載連接是:https://github.com/xitu/gold-miner/blob/master/TODO/building-a-shop-with-sub-second-page-loads-lessons-learned.md