美國的創智贏家(Shark Tank),英國的龍穴之創業投資(Dragons’ Den),以及德國的「Die Hohle der Lowen (DHDL)」等電視節目爲年輕的初創公司提供了一個在海量觀衆面前向商業巨頭展示本身產品的機會。然而這些初創公司的主要收益一般並不在於評委提供的戰略性投資,畢竟只有不多數交易最終可以完成,而在於經過電視節目得到的關注:電視上出現幾分鐘的畫面一般就能夠爲網站吸引成百上千的新訪客,藉此能夠有效提高每週、每個月,甚至更長時間內的網站流量。然而前提是,網站必需要能承受一開始的負載尖峯不能掉鏈子……php
網店們一般會發現本身處於一種很是尷尬的境地,由於他們不只僅是一些消遣性的項目(例如博客),而是一般須要由創始人揹負大量投資,而且必須千方百計盈利。對企業來講,最糟糕的狀況莫過於服務器超載致使沒法處理用戶請求,甚至整個網站完全崩潰。這種狀況其實比你想象得更常見:當季DHDL節目中露臉的全部網店中,約有半數由於上電視後不堪重負而崩潰。而能繼續保持在線也只是走完了萬里長征一半的路程,由於用戶滿意度直接決定了轉化率,也就是說,用戶滿意度直接決定了最終能得到的銷售額。css
來源:http://infographicjournal.com/how-page-load-time-can-impact-conversions/html
針對網頁加載時間對客戶滿意度和轉化率的影響已經有太多研究,這足以證實這些因素的重要性。例如Aberdeen Group發現延遲每增長1秒會致使頁面訪問量減小11%,轉化率下降7%。就算你去問谷歌或者亞馬遜,他們的結論也是相同的。前端
在給初創公司Thinks開發網店(該公司參加了9月6日的DHDL節目)的過程當中,咱們遇到了一個挑戰:要求所開發的網站能在順利迎接成千上萬訪客的同時實現不超過1秒的加載速度,這對咱們而言是一個巨大的挑戰。從這個項目的實施,以及在數據庫和Web性能領域多年的研究成果中,咱們得到了不少寶貴的經驗。jquery
影響Web應用程序頁面加載速度最主要的因素有三個,以下圖所示。git
爲了給網店提速,須要逐個消除這些性能瓶頸。github
影響前端性能最重要的因素在於關鍵呈現路徑(Critical Rendering Path,CRP)。這個概念描述了瀏覽器將網頁呈現給用戶的5個必要步驟,以下圖所示。web
關鍵呈現路徑所涉及的步驟算法
上述每一個步驟單獨看都至關簡單,可是不一樣步驟之間的依賴性使得事情變得複雜,並會對性能產生影響。DOM和CSSOM的構造一般會對性能產生最大影響。sql
下圖展現了一個關鍵呈現路徑,箭頭所指內容爲須要花時間等待的依賴項。
關鍵呈現路徑中重要的依賴項
在加載CSS並構造完整CSSOM以前,客戶端上什麼都不會顯示。所以CSS也被稱之爲「呈現」的攔路虎。
JavaScript(JS)的狀況更糟,由於不管DOM或CSSOM均可被JavaScript所訪問並更改。這意味着一旦在HTML中發現Script標記,DOM的構造過程將暫停,等待從服務器請求腳本。在腳本加載完以後,還須要等待取回全部CSS並完成CSSOM的構造,隨後才能繼續執行。例如在下面的例子中,當CSSOM構造完成而且JS執行完畢後,終於能夠訪問並修改DOM和CSSOM了。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進行優化,須要考慮三個基本概念:
此外瀏覽器緩存是一種始終值得考慮使用的很是有效的作法。這種方法適合用於上述三種類型內容的優化,由於在將資源緩存後,就無需像第一次訪問那樣從服務器加載。
CRP優化是一個很是複雜的話題,尤爲是內聯、合併,以及異步加載等措施會完全毀掉代碼的可讀性。好在目前有不少實用的工具能夠幫助咱們進行優化,能夠考慮將其集成到本身的構建和部署流程中。下面這幾個工具絕對值得一試…
在這些工具的幫助下,只須少許工做便可打造出前端性能更出色的網站。訪客首次訪問Thinks網店的頁面加載速度測試結果以下:
Google PageSpeed對thinks.com網站的評測結果
有趣的是,PageSpeed Insights中檢測到惟一存在問題的地方竟在於Google Analytics腳本的緩存壽命太短。因此谷歌基本上就是在抱怨自家的問題咯。
從加拿大(GTmetrix)訪問位於法蘭克福的服務器時的首次頁面加載速度
網絡延遲是網頁加載速度最大的影響因素,同時也是最難優化的。但在實際進行優化前先來看看瀏覽器首次請求分解後的步驟:
在瀏覽器中輸入 https://www.thinks.com/ 並按下回車後,瀏覽器首先經過DNS查詢解析該域名對應的IP地址。訪問每一個域名都須要進行這樣的查詢。
在得到IP地址後,瀏覽器發起一個到服務器的TCP鏈接。TCP握手須要2次往返(1次可以使用TCP Fast Open)。對於安全的SSL鏈接,TLS握手須要額外的2次往返(1次可以使用TLS False Start或Session Resumption)。
初始鏈接完成後,瀏覽器發出實際請求並等待返回的數據。得到首個字節所需的時間主要取決於客戶端和服務器之間的距離,其中還包含服務器呈現網頁(包括會話查詢、數據庫查詢、模板呈現等環節)所需的時間。
最後一步須要下載資源(本例中爲HTML)這一過程須要屢次往返。尤爲是新鏈接一般須要更多往返,由於初始擁塞窗口一般很小。這意味着TCP並不能在一開始就全面使用全部可用帶寬,而是會隨着時間流逝逐漸增長帶寬用量(可參閱TCP擁塞控制)。具體速度受制於啓動速度緩慢的算法,這種狀況下會讓每一個往返的擁塞窗口內片斷數量翻倍,直到數據包真正開始丟失。在移動和Wifi網絡中,由於這種狀況致使的數據包丟失會對性能產生極大影響。
另外還要注意:在使用HTTP/1.1的狀況下,最多隻能建立6個併發鏈接(若是瀏覽器依然沿襲了最初的標準,則最多隻能建立2個鏈接)。所以最多隻能並行請求6個資源。
爲了更直觀地瞭解網絡性能會對頁面加載速度產生多大影響,httparchive提供了大量統計數據。例如平均來講,每一個網站須要用超過100個請求才能加載大約2.5MB的數據。
來源:http://httparchive.org/interesting.php#reqTotal
也就是說,網站須要用大量小請求加載數量衆多的資源,但網絡帶寬不是總在增長嗎?最終能夠經過寬帶提速等措施解決這一問題對吧?未必…
來自《High Performance Browser Networking》,做者Ilya Grigorik
實際上帶寬超過5Mbps後將沒法繼續對網頁加載速度產生任何改善。可是下降每一個請求的延遲能夠大幅加快網頁加載速度。這意味着帶寬翻倍後加載時間依然不會有變化,但延遲減半可讓網頁加載速度提升一倍。
既然延遲是網絡性能的決定性因素,咱們能作些什麼?
簡而言之,網絡延遲方面有一些必作,以及絕對不能作的事,但總的來講,主要侷限因素依然在於往返的次數以及物理網絡的延遲。破解這一侷限惟一有效的方式是拉近數據和客戶的距離。先進的Web緩存技術就是用來作這種事的,但這種技術只能用於靜態資源。
對於Thinks,咱們徹底遵守上述原則使用了Fastly CDN以及更激進的瀏覽器緩存機制,甚至動態數據也使用了一種新穎的Bloom Filter算法確保緩存數據的一致性。
重複對www.thinks.com進行加載測試可瞭解瀏覽器緩存的覆蓋率
反覆進行頁面加載測試的過程當中,惟一未能從瀏覽器緩存完成的請求(見上圖)是兩個對谷歌分析API進行的異步調用,初始HTML的請求則是從CDN獲取的。所以在反覆進行的測試中,頁面加載速度有了顯著提升。
在後端性能方面,咱們須要同時考慮延遲和吞吐率。爲了實現低延遲,咱們須要將服務器的處理工做所需時間降至最低。爲了實現更高吞吐率並應對負載尖峯,咱們須要採起一種能夠橫向縮放的架構。雖然不許備深刻介紹太多細節,但設計方面對性能產生的影響是極爲巨大的,須要重點關注的組件和屬性包括:
可縮放後端棧包含的組件:負載均衡器、無狀態應用程序服務器、分佈式數據庫
首先須要負載均衡(例如Amazon ELB或DNS負載均衡),藉此將傳入的請求分配至多臺應用程序服務器中的一臺。此外還能夠實施自動縮放以便在須要時添加額外的應用程序服務器,並經過故障轉移機制替換故障服務器,將請求從新路由至正常運轉的服務器。
爲將協調的次數降至最低,應用程序服務器應當維持最小化的共享狀態,並可以使用無狀態會話處理機制實現更自由的負載均衡。此外服務器的代碼和IO應儘量高效,藉此便可將服務器處理時間降至最低。
數據庫也須要能應對尖峯期的負載,並儘可能將處理工做所需的時間降至最低。與此同時,數據庫須要具有建模和數據查詢需求所需的能力。市面上有大量可縮放的數據庫(尤爲是NoSQL數據庫),每種產品都各有利弊。更多詳情可參閱咱們有關該話題的調查和決策指南:NoSQL數據庫:調查和決策指南。
Thinks的網店基於Baqend構建,使用了下列後端棧:
Baqend的後端棧:MongoDB充當的主數據庫、無狀態應用程序服務器、HTTP緩存體系,以及適用於Web前端的REST和JS SDK
Thinks使用MongoDB做爲主數據庫。爲了實現會在短期內過時的Bloom篩選器(用於瀏覽器緩存),咱們出於更高寫入吞吐率的考慮使用了Redis。無狀態應用程序服務器(Orestes Servers)提供了用於訪問後端功能(文件託管、數據存儲、實時查詢、推送通知、訪問控制等)的接口,並由該服務器負責處理動態數據的緩存連貫性。這些服務器可經過充當負載均衡器的CDN發起請求。網站前端使用了一種基於REST API的JS SDK訪問後端,後端可自動利用完整的HTTP緩存體系對請求進行加速,並確保緩存數據處於最新狀態。
爲了對Thinks網店面對高負載狀況的表現進行測試,咱們使用位於法蘭克福的兩個t2.medium AWS實例做爲應用程序服務器進行了負載測試。MongoDB運行在兩個t2.large實例上。咱們的負載測試使用20臺IBM SoftLayer計算機運行JMeter,藉此模擬200000用戶在15分鐘內訪問該網站的負載。其中20%的用戶(40000個)被配置爲額外執行支付操做。
該網店的負載測試環境
測試發現支付系統方面存在幾個瓶頸,例如咱們必須從本來對庫存進行樂觀更新(經過findAndModify實現)的作法改成使用MongoDB的部分更新操做(inc)。但在這以後服務器面對負載表現很是出色,實現了平均5ms的請求延遲。
JMeter負載測試結果:12分鐘內680萬個請求,平均延遲5ms
全部負載測試總共生成了大約1千萬個請求並傳輸了460GB數據,CDN的緩存命中率高達99.8%。
負載測試以後的儀表盤概述信息
簡而言之,良好的用戶體驗須要從三方面來實現:前端、網絡,以及後端性能。
前端性能在咱們看來是最容易實現的,由於市面上已經有不少現成的工具以及各類最佳實踐,照作很容易就能搞定。但依然有不少網站沒有參照這些最佳實踐,甚至徹底沒有對前端進行任何優化。
網絡性能是頁面加載速度的最大影響因素,同時也是最難優化的。緩存和CDN是最有效的優化方法,但須要注意到,這些機制只能對靜態內容進行優化。
後端性能主要取決於單臺服務器的性能以及分佈式環境的規模。橫向擴展很是難以實現,所以從一開始就要妥善考慮。不少項目將縮放能力和性能放在最後考慮,隨着業務的增加最終將遇到很是棘手的問題。
圍繞Web性能和可縮放系統的設計,目前有不少不錯的圖書。Ilya Grigorik撰寫的《High Performance Browser Networking》一書幾乎涉及了有關網絡和瀏覽器性能的方方面面,此外本書還提供了一個持續更新,可免費在線閱讀的版本!Martin Kleppmann撰寫的《Designing Data-Intensive Applications》雖然目前尚處於早期發佈階段,但已經成爲該領域最棒的圖書。其中談及了大量可縮放後端系統內部的技術基本知識,幷包含不少細節介紹。Lara Callender Hogan撰寫的《Designing for Performance》談到了如何構建速度快,可提供出色用戶體驗的網站,並提供了大量最佳實踐。
此外還有大量在線指南、教程和工具可供借鑑。從面向新手的Udacity課程網站性能優化到谷歌的開發者性能指南,再到諸如Google PageSpeed Insights、GTmetrix以及WebPageTest等分析工具,都能爲咱們提供巨大的幫助。
谷歌正在經過PageSpeed Insights、開發者指南等有關Web性能的項目幫助你們提升對Web性能的重視,甚至將網頁加載速度做爲該公司頁面排名的一個重要依據。
谷歌搜索在改善頁面加載速度和用戶體驗方面的最新項目名爲移動頁面加速(AMP)。該項目意在讓新聞文章、產品頁面,以及其餘搜索內容能經過谷歌搜索結果馬上呈現。爲此必須以AMP的方式構建這些頁面。
AMP頁面範例
AMP主要作了兩件事:
第一件事最爲關鍵,AMP會經過某種方式對HTML、JS和CSS內容進行限制,使得經過這種方式構建的頁面能夠得到最優化的關鍵呈現路徑,並能被谷歌爬蟲更輕鬆地爬網檢索。AMP會強制實施多種限制,例如全部CSS必須是內聯的,全部JS必須是異步的,頁面上包含的一切內容必須爲固定大小(這是爲了不「重繪」)。雖然就算無需這些限制,按照上文提到的Web性能最佳實踐也能夠實現相同結果,但AMP也許是一種更好的作法,就算一些很簡單的網站也可使用。
第二件事意味着谷歌會在對你的網站進行爬網檢索的同時將內容緩存在Google CDN中,藉此實現更快速的交付。當爬網程序再次索引你的網站時,緩存的網站內容會被更新。該CDN還會遵照服務器設置的靜態TTL,但與此同時至少會進行微緩存(Micro-caching):認爲資源在至少一分鐘以內是新鮮的,並在收到用戶請求後在後臺對資源進行更新。這樣的作法使得AMP最適合網站以靜態內容爲主的用例,例如以人工方式進行內容編輯的新聞網站或其餘用於出版發佈用途的網站。
另外一種方法(一樣由谷歌提出)是Progressive Web Apps(PWA)。該方式意在瀏覽器中使用服務工做進程(Service worker)對網站中的靜態部分建立緩存。經過這種方式,從新查看網頁時就能夠當即加載緩存的內容,並可脫機使用。但網站上的動態內容依然須要從服務器加載。
應用外殼(App shell)(單頁應用程序的邏輯)可在後臺從新驗證。若是發現應用外殼有更新,會經過一則消息要求用戶更新頁面。例如Gmail的Inbox就是這樣作的。
然而服務工做進程的代碼會對每一個網站的靜態資源進行緩存並進行從新驗證,這會產生不小的開銷。此外目前僅Chrome和Firefox對服務工做進程提供了完善的支持。
全部緩存方法都會遇到一個問題:沒法處理動態內容。這主要是因爲HTTP緩存的工做原理致使的。緩存主要有兩種類型:基於失效(Invalidation-based)的緩存(例如轉發代理緩存和CDN),以及基於過時(Expiration-based)的緩存(例如ISP緩存、企業代理以及瀏覽器緩存)。基於失效的緩存可由服務器主動指定爲失效,基於過時的緩存只能由客戶端進行從新驗證。
在使用基於失效的緩存時,最棘手的問題在於,首次將數據從服務器交付給客戶端時,必須指定緩存的壽命(TTL)。隨後對於已經緩存的數據就徹底喪失了控制能力。在TTL過時前,瀏覽器將始終是用當時緩存的內容。對於靜態內容來講,這個問題並非很麻煩,由於一般這類內容只在部署新版本Web應用程序以後纔會有改動。所以可使用諸如gulp-rev-all和grunt-filerev等實用工具對這些資產建立哈希。
但對於會在應用程序運行過程當中加載和改動的各種數據又該怎麼辦?更改用戶資料,更新已發佈的內容,或發佈新的評論,這些操做涉及的內容彷佛沒法與瀏覽器緩存很好地結合在一塊兒,由於咱們沒法預計此類更新會發生在將來的何時。此時只能禁用緩存,或使用很是短的TTL。
被其餘客戶端更新後,緩存的動態數據變爲陳舊狀態的範例
Baqend研究並開發了一種在客戶端實際獲取以前檢查URL陳舊度的方法。在啓動每一個用戶會話後,咱們首先會獲取一個很是小的數據結構,該結構名爲Bloom篩選器,是一種高度壓縮的內容,其中描述了全部陳舊的資源。經過查詢Bloom篩選器,客戶端能夠知道每一個資源是否陳舊(包含在Bloom篩選器中)或肯定其爲新鮮狀態。對於有可能陳舊的資源,咱們會繞過瀏覽器緩存從CDN獲取相應的內容。在其餘任何狀況下,咱們會直接經過瀏覽器緩存提供內容。使用瀏覽器緩存能夠減小網絡通訊並節約帶寬,同時速度至關快。
此外經過在資源變得陳舊後馬上進行清理,確保了CDN(以及其餘基於失效的緩存,例如Varnish)始終包含最新數據。
Baqend保障被緩存動態數據新鮮度的範例
Bloom篩選器是一種基於機率的數據結構,並使用了可調整的假陽性率(False positive rate),這意味着該數據集可能會對從未加入過的對象進行限制,但絕對不會漏掉任何一個須要限制的對象。換句話說,咱們可能偶爾會對新鮮資源進行從新驗證,但絕對不會交付陳舊的數據。另外值得注意的是,假陽性率很是低,藉此纔可讓整個數據集的規模儘量小。例如咱們只須要11K字節的數據集就能夠存儲20000個不一樣的更新。
服務器端會進行大量流處理(查詢匹配檢測)、機器學習(優化TTL的估算),以及分佈式協調(可縮放Bloom篩選器的維護)。若是你對相關細節感興趣可參閱這篇論文或這些幻燈片以進一步詳細瞭解。
諸多努力就是爲了改善性能。
Baqend的緩存基礎架構能夠經過哪些方面改善網頁加載速度?
爲了展現Baqend技術對性能的改進,咱們經過後端即服務(BaaS)領域每一個主要競爭對手的平臺構建了一個很是簡單的新聞應用程序,並測試了從全球不一樣位置加載頁面所需的時間。以下圖所示,Baqend技術的頁面加載速度始終不超過1秒,平均來講是競爭對手速度的6.8倍。就算全部客戶端均與服務器位於同一個位置,因爲瀏覽器緩存技術的使用,Baqend的速度也快了150%。
經過一個簡單的新聞應用程序對平均加載時間進行的比較
爲了對BaaS競爭對手的服務進行比較,咱們還構建了一個可供你們實際操做進行比較的Web應用。
實際操做比較結果的屏幕截圖
固然,這只是一個測試場景,並不是有真實用戶的Web應用程序。咱們從新回到Thinks網店這個例子,看看真實環境中的表現到底如何吧。
當DHDL(德國版的「創智贏家」電視節目)在9月6日首播並吸引了270萬觀衆時,咱們正坐在電視機前密切關注着Google Analytics的統計信息,同時還在爲Thinks創始人所介紹的產品而感到激動。
從他們介紹自家產品開始,網店的併發用戶數在短期內增長了大約10000人,但真正的尖峯發生在節目插播廣告的時候,忽然之間超過45000個併發用戶涌入網店打算購買Towell+:
從插播廣告前一刻開始的Google Analytics統計結果
在Thinks上電視的30分鐘內,咱們收到了340萬請求,300000個訪客,高達50000個併發訪客,以及每秒最高20000個請求——全部這一切都在CDN層面實現了98.5%的緩存命中率以及平均3%的服務器CPU負載。
總的來講,全時段內低於1秒的頁面加載時間最終產生了高達7.8%的轉化率,這個結果讓人很滿意。
若是再看看同一期DHDL節目中介紹的其餘商家,咱們會發現其中有四家完全宕機,其他幾個商家僅使用了微不足道的性能優化技術。
9月6日播出的DHDL節目中推薦的商家的總體可用性和谷歌頁面加載速度評分
在設計快速可縮放網站過程當中,咱們解決了不少性能瓶頸:咱們全面掌握了關鍵呈現路徑,充分理解了網絡方面的限制和緩存的重要性,並設計出一套可橫向縮放的後端系統。
咱們發現有不少實用工具很適合用來解決某些具體的問題,此外還能夠經過移動頁面加速(AMP)和Progressive Web Apps(PWA)實現更全面的優化。但動態數據的緩存這個問題依然存在。
Baqend採起的方法是儘可能減小前端Web開發的工做量,經過JS SDK從全面託管的Baqend雲服務得到所需後端功能,包括數據和文件的存儲、(實時)查詢、推送通知、用戶管理、OAuth,以及訪問控制。經過使用完整的HTTP緩存體系,該平臺能夠自動加速全部請求,同時可用性與可縮放性也更有保障。