網站前端優化技術 BigPipe分塊處理技術

前端優化已經到極致了麼?業務還在爲看到不停的而揪心麼?還在爲2秒率不達標苦惱麼?javascript

好吧我知道答案,你們一如既往的煩惱中。。。php

那麼接下來咱們看看,facebook,淘寶,人人網,一淘都是怎麼作前端優化的,他們頁面信息量比咱們大的多。css

 

前段時間泳洋和我提了flush技術,業界不少公司都在這麼用。終於找時間來好好研究了一番,得到這個新名詞----BigPipe,什麼是BigPipe? 接着往下看。html

 

1. 技術背景 FaceBook頁面加載技術前端

試想這樣一個場景,一個常常訪問的網站,每次打開它的 頁面都要要花費6 秒;同時另一個網站提供了類似的服務,但響應時間只需3 秒,那麼你會如何選擇呢?數據代表,若是用戶打開一個網站,等待3~4 秒尚未任何反應,他們會變得急躁,焦慮,抱怨,甚相當閉網頁而且再也不訪問,這是很是糟糕的狀況。因此,網頁加載的速度十分重要,尤爲對於擁有遍及全球的 5億用戶的Facebook(全球最大的社交服務網站)這樣的大型網站,有着大量併發請求、海量數據等客觀狀況,速度就成了必須攻克的難題之一。java

2010 年初的時候,Facebook 的前端性能研究小組開始了他們的優化項目,通過了六個月的努力,成功的將我的空間主頁面加載耗時由原來的5 秒減小爲如今的2.5 秒。這是一個很是了不得的成就,也給用戶來帶來了很好的體驗。在優化項目中,工程師提出了一種新的頁面加載技術,稱之爲Bigpipe。目前淘寶和 Facebook 面臨的問題很是類似:海量數據和頁面過大,若是能夠在詳情頁、列表頁中使用bigpipe,或者在webx中集成bigpipe,將會帶來明顯的頁面加載 速度提高。web

2. 相關介紹編程

2.1 網站前端優化的重要性json

《高性能網站建設指南》一書中指出,只有 10%~20%的最終用戶響應時間是花費在從Web 服務器獲取HTML 文檔並傳送到瀏覽器中的。若是但願可以有效地減小頁面的響應時間,就必須關注剩餘的80%~90%的最終用戶體驗。作個比較,若是對後臺業務邏輯進行優 化,效率提升了50%,但最終的頁面響應時間只減小了5%~10%,由於它所佔的比重較少。若是對前端進行性能優化,效率提高50%,則會使最終頁面響應 時間減小40%~45%。這是多麼可觀的數字!另外,前端的性能優化通常比業務邏輯的優化更加容易。因此,前端優化投入小,見效快,性價比極高,須要投入 更多的關注。數組

2.2 BigPipe與AJAX

Web2.0的重要特徵是網頁顯示大量動態內容,即 web2.0注重網頁與用戶的交互。其核心技術是AJAX,現在全部主流網站都或多或少使用AJAX。與AJAX相似,BigPipe 實現了分塊兒的概念,使頁面可以分步輸出,即每次輸出一部分網頁內容。接下來討論BigPipe 與AJAX 的區別。

簡單的說,BigPipe 比AJAX 有三個好處:

1. AJAX 的核心是XMLHttpRequest,客戶端須要異步的向服務器端發送請求,而後將傳送過來的內容動態添加到網頁上。如此實現存在一些缺陷,即發送往返 請求須要耗費時間,而BigPipe 技術使瀏覽器並不須要發送XMLHttpRequest 請求,這樣就節省時間損耗。

2. 使用AJAX時,瀏覽器和服務器的工做順序執行。服務器必須等待瀏覽器的請求,這樣就會形成服務器的空閒。瀏覽器工做時,服務器在等待,而服務器工做時, 瀏覽器在等待,這也是一種性能的浪費。使用BigPipe,瀏覽器和服務器能夠並行同時工做,服務器不須要等待瀏覽器的請求,而是一直處於加載頁面內容的 工做階段,這就會使效率獲得更大的提升。

3. 減小瀏覽器發送到請求。對一個5億用戶的網站來講,減小了使用AJAX額外帶來的請求,會減小服務器的負載,一樣會帶來很大的性能提高。

基於以上三點,Facebook 在進行頁面優化時採用了BigPipe 技術。目前淘寶主搜索結果頁中,須要加載類目,相關搜索,寶貝列表,廣告等內容,前端這裏使用php 的curl 的批處理來併發的訪問引擎獲取相應的數據,並進行分步輸出。這種模式仍是與bigpipe有些不一樣,這點後面會講到。通常來說,在頁面比較大,並且比較復 雜,樣式表和腳本比較多的狀況下,使用BigPipe 來優化輸出頁面是比較合適的。另外很是重要的一點,BigPipe 並不改變瀏覽器的結構與網絡協議,僅使用JS就能夠實現,用戶不須要作任何的設置,就會看到明顯的訪問時間縮短。

3   目前的問題

接下來討論現有的瓶頸。面對網頁愈來愈大的狀況,尤爲是大量的css 文件和js 文件須要加載,傳統的頁面加載模型很難知足這樣的需求,直接結果就是頁面加載速度變慢,這毫不是咱們但願看到的。目前的技術實現中,用戶提出頁面訪問請求後,頁面的完整加載流程以下:

1. 用戶訪問網頁,瀏覽器發送一個HTTP 請求到網絡服務器

2. 服務器解析這個請求,而後從存儲層去數據,接着生成一個html 文件內容,並在一個HTTP Response 中把它傳送給客戶端

3. HTTP response 在網絡中傳輸

4. 瀏覽器解析這個Response ,建立一個DOM 樹,而後下載所需的CSS 和JS文件

5. 下載完CSS 文件後,瀏覽器解析他們而且應用在相應的內容上

6. 下載完JS 後,瀏覽器解析和執行他們

圖1.

完整流程見圖1.圖中左側表示服務器,右側表示瀏覽 器。瀏覽器先發送請求,而後服務器進行查找數據,生成頁面,返回html 代碼,最後瀏覽器進行渲染頁面。這種模式有很是明顯的缺陷:流程中的操做有着嚴格的順序,若是前面的一個操做沒有執行結束,後面的操做就不能執行,即操做 之間是不能重疊。這樣就形成性能的瓶頸:服務器生成一個頁面的內容時,瀏覽器是空閒的,顯示空白內容;而當瀏覽器加載渲染頁面內容時,服務器又是空閒的, 時間與性能的浪費由此產生。

圖2.

考慮圖2 中現有的服務模型,橫軸表示花費的時間。黃色表示在服務器的生成頁面內容的時間,白色表示網絡傳輸時間,藍色表示在瀏覽器渲染頁面的時間。能夠看出,現有 的模式形成很大的時間浪費。 考慮圖3 中的狀況,圖中綠色表示服務器從春儲層取查數據花費的時間,在海量數據下,當執行一條很費時的查詢語句時(以下圖右側),服務器就就阻塞在那 裏沒有其餘操做,而瀏覽器更是得不到任何反饋。這會形成很是不友好的用戶體驗,用戶不知道什麼緣由使他們等待很長時間。

圖3.

4    BigPipe思想與原理

面對上述問題,咱們看下BigPipe的解決辦法。 BigPipe提出分塊的概念,即根據頁面內容位置的不一樣,將整個頁面分紅不一樣的塊兒– 稱爲pagelet。該技術的設計者Changhao Jiang 是研究電子電路的博士,可能從微機上獲得了啓發,將衆多pagelet加載的不一樣階段像流水線同樣在瀏覽器和服務器上執行,這樣就作到了瀏覽器和服務器的 並行化,從而達到重疊服務器端運行時間和瀏覽器端運行時間的目的。使用BigPipe 不只能夠節省時間,使加載的時間縮短,並且能夠同過pagelet的分步輸出,使一部分的頁面內容更快的輸出,從而得到更好的用戶體驗。BigPipe 中,用戶提出頁面訪問請求後,頁面的完整加載流程以下:

1. Request parsing:服務器解析和檢查http request

2. Datafetching:服務器從存儲層獲取數據

3. Markup generation:服務器生成html 標記

4. Network transport : 網絡傳輸response

5. CSS downloading:瀏覽器下載CSS

6. DOM tree construction and CSS styling:瀏覽器生成DOM 樹,而且使用CSS

7. JavaScript downloading: 瀏覽器下載頁面引用的JS 文件

8. JavaScript execution: 瀏覽器執行頁面JS代碼

這個8 個流程幾乎與上文中提到現有的模式沒有區別,但這整個流程只是一個pagelet 的完整流程,而多個pagelet 的不一樣操做階段就能夠像流水線同樣進行執行了。

圖4

圖4 中,能夠看出BigPipe 對原有的模式進行的改進。瀏覽器發送訪問請求,而後瀏覽器分步返回不一樣的pagelet的內容,具體實現將在後面介紹.考慮圖5中的改進,BigPipe 打破了原有的順序執行,將頁面分紅不一樣的pagelet ,如此一來,全部的pagelet 的執行時間累加起來仍是原有的時間。可是, 經過疊加不一樣pagelet 的不一樣階段的執行時間,使總的運行時間大大減小,這就是Bigpipe減小頁面加載時間的祕密。

 

 

BigPipe 實現原理

瞭解了BigPipe 的核心思想後,咱們討論它的實現原理。當瀏覽器訪問服務器時,服務器接受請求並對其進行檢查。若是請求有效,服務器端不作任何的查詢,而是馬上返回一個 http request 給瀏覽器,內容是一段html 代碼,包括html<head> 標籤和<body> 標籤的一部分。<head>標籤包括BigPipe 的js文件和css文件,這個js 文件用來解析後面接收的http response,由於後面傳輸的內容都爲js腳本。未封閉的<body>標籤中,是顯示頁面的邏輯結構和pagelet 的佔位符的模板,例如:

<body>

<div></div>

<div></div>

<div></div>

<div>

<div>

<div id=」hotnews」></div>

<div id=」societynews」></div>

<div id=」financialnews」></div>

<div id=」ITnews」></div>

<div id=」sportsnews」></div>

</div>

<div></div>

</div>

<div></div>

上述模板使用css-div 描述了頁面的結構,不一樣的div 標籤對應不一樣的pagelet,id 對應了pagelet 的名稱。將這個response 返回給瀏覽器後,服務器開始對每一個pagelet 的內容進行查詢,加載,生成。當一個pagelet的內容生成好,馬上調用flush()函數,將其返回給客戶端,傳輸的數據是以json 格式的,包括這個pagelet 須要的CSS 和JS,以及html 內容和一些元數據。例如:

<script type=」text/javascript」>

big_pipe.onPageletArrive(

{id:」pagelet_composer」,

content:」<HTML>」,

css:」[..]「,

js:」[..]「,

…}

);

</script>

其中」content」表示這個pagelet 的內容,是html 源碼,特殊字符如「」/須要進行轉義;」id」表示content要顯示的位置,即爲對應的pagelet 的id標籤;」css」表示須要下載的CSS 資源的路徑;」js」表示須要下載的JS 腳本的路徑。爲了不文件路徑過長,因此在前面須要對css 和js 文件的路徑進行轉換,轉換後爲5 位字符串:不一樣的pagelet 可能會加載同一個css 或js 文件,因此要避免重複下載。

雖然每一個pagelet 都有要加載的js 文件,可是全部的js 文件都是在最後加載,這樣有利於加快頁面加載速度。客戶端,當經過調用「onPageletArrive(json)」函數,第一次影響傳輸的JS腳本中 的函數解析了傳入的json 數據,接着下載須要的CSS,而後把html 內容顯示到響應的DIV 標籤位置上。多個pagelets 的CSS文件能夠同時下載,CSS 下載完成的pagelet 先顯示。

在BigPipe 中,js 被給予了比CSS 和content 更低的優先級。這樣, 只有當全部的pagelets 都顯示了,BigPipe 纔開始去下載JS 文件。全部的JS 文件都下載完成後,Pagelets的JS初始化代碼開始執行,按照下載完成時間的前後順序。在這個高度並行的系統中,幾個的pagelet 所要執行的不一樣的階段能夠同時執行。例如,瀏覽器能夠給兩個pagelets 下載CSS 資源,同時瀏覽器能夠渲染另一個pagelet 的內容,同時服務器仍然在爲另外一個pagelet 生成html源碼。從用戶的角度看來,頁面時逐步呈現的。初始的頁面顯示的更快,能夠有效減短用戶感受到的延遲。

BigPipe 實現問題討論

6.1  服務器端的並行化

理想狀況下,服務器端的實現是並行處理不一樣的 pagelet 的內容,這樣能夠提高性能。服務器併發處理多個pagelet 的內容時,一個pagelet 內容生成好了,馬上將其flush 給瀏覽器。可是PHP 是不支持線程,因此服務器沒法利用多線程的概念去併發的加載多個pagelet 的內容。對於小型網站來講,使用串行的加載pagelet 的內容就已經能夠達到優化的要求了。對於大型網站,爲了達到更快的速度,服務器端能夠選擇併發的獨立不一樣的pagelet 的內容,具體實現有如下幾種方式:

  1. java 多線程。後臺邏輯使用java,可使用java 的多線程機制去同時加載不一樣的pagelet 的內容,加載完成後加頁面內容返回給瀏覽器。在最後的引用部分能夠看到網上用java多線程實現的例子。
  2. .net 同理

6.2  直接調用flush 函數輸出

到這裏,可能會有這樣的疑問,爲什服務器不直接把生成好的HTML 內容分部flush() 返回給客戶端,而是使用json 格式傳遞,而後用js 解析呢?這不是畫蛇添足麼?實際上,這也是目前主搜索前端使用的方法。咱們看看使用BigPipe方式的兩大好處:

(1) 若是直接調用flush()函數輸出html 源碼,當模塊較多的狀況,模塊間必須按順序加載,在html 前面的模塊必須先加載完,後面的才能加載,這樣也就沒辦法每一個模塊同時顯示一些內容。例以下面的html:

上面3 個div 分別表明3 個模塊,若是直接分部輸出html ,服務器端必須先加載完畢div1 模塊中的內容並flush 出去後,才能繼續加載div2的內容,若是flush 順序不同,輸出的html 結構確定就會出問題,這樣就致使前臺頁面沒辦法同時顯示3 個loading。由於這樣flush 必需要有前後順序。而若是採用JS 的話,能夠前臺顯示3 個loading,並且不須要關心到底哪一個模塊先加載完,這樣還能發揮後臺多線程處理數據的優點。

(2)使用JS 這種方式能夠是頁面結構更加清晰,管理更加方便。同時作到了頁面邏輯結構和數據解耦,首先返回的是頁面的結構,接着不斷地返回js腳本,而後動態添加頁面內容,而不是全部完整的html 源碼一塊兒輸出,增長了可維護性。

6.3  訪問者是爬蟲或者訪問者瀏覽器禁止使用JS 的狀況

咱們知道BigPipe 使用js 腳本加載頁面,那麼當用戶在瀏覽器裏設置禁止使用js 腳本(雖然人數不多),就會形成加載頁面失敗,這一樣是很是很差的用戶體驗。對搜索引擎的爬蟲來說,一樣會遇到相似的問題。解決辦法是當用戶發送訪問請求 時,服務器端檢測user-agent 和客戶端是否支持js 腳本。若是user-agent 顯示是一個搜索引擎爬蟲或者客戶端不支持js,就不使用BigPipe ,而用原有的模式,從而解決問題。

6.4  SEO 的影響

這是一個必須考慮的問題,現在是搜索引擎的時代,若是 網頁對搜索引擎不友好,或者使搜索引擎很難識別內容,那麼會下降網頁在搜索引擎中的排名,直接減小網站的訪問次數。在BigPipe 中,頁面的內容都是動態添加的,因此可能會使搜索引擎沒法識別。可是正如前面所說,在服務器端首先要根據user-agent 判斷客戶端是不是搜索引擎的爬蟲,若是是的話,則轉化爲原有的模式,而不是動態添加。這樣就解決了對搜索引擎的不友好。

6.5  融合其餘技術

除了使用BigPipe,Facebook的頁面加載技術還融合了其餘的頁面優化技術,具體以下:

6.5.1  資源文件的G-zip 壓縮

這是很是重要的技術,使用G-zip 對css 和js 文件壓縮可使大小減小70%,這是多麼誘人的數字!在網絡傳輸的文件中,主要就是樣式表和腳本文件。如此能夠大大減少傳輸的內容,使頁面加載速度變得更 快。具體實現能夠藉助服務器來進行,例如Apache,使用mod_deflate 模塊來完成具體配置爲: AddOutputFilterByType DEFLATE text/html text/css application/xjavascript

6.5.2  js 文件進行了精簡

對js 文件進行精簡,能夠從代碼中移除沒必要要的字符,註釋以及空行以減少js 文件的大小,從而改善加載的頁面的時間。精簡js 腳本的工具可使用JSMin,使用精簡後的腳本的大小會減小20%左右。這也是一個很大的提高。

6.5.3  css js 文件進行合併

這是前端優化的一項原則,將多個樣式表和js 文件進行合併,這樣的話,將會減小http 的請求個數。對於上億用戶的網站來講,這也會帶來性能的提高,大約會減小5%左右的時間損耗。

6.5.4  使用外部JS CSS

一樣是前端優化的一項原則。純粹就速度來言,使用內聯 的js 和css 速度要更快,由於減小了http 請求。可是,使用外部的文件更有利於文件的複用,這與面向對象編程的概念很像。更爲重要的是,雖然在第一次的加載速度慢一點,但css 文件和js腳本是能夠被瀏覽器緩存。即以後用戶的屢次訪問中,使用外部的js 和css 將會將會更好的提高速度。

6.5.5  將樣式表放在頂部

和上面內容類似,這也是一種規範,將html 內容所需的css 文件放在首部加載是很是重要的。若是放在頁面尾部,雖然會使頁面內容更快的加載(由於將加載css 文件的時間放在最後,從而使頁面內容先顯示出來),可是這樣的內容是沒有使用樣式表的,在css 文件加載進來後,瀏覽器會對其使用樣式表,即再次改變頁面的內容和樣式,稱之爲「無樣式內容的閃爍」,這對於用戶來講固然是不友好的。實現的時候將css 文件放在<head>標籤中便可。

6.5.6  將腳本放在底部實現「barrier」

支持頁面動態內容的Js 腳本對於頁面的加載並無什麼做用,把它放在頂部加載只會使頁面更慢的加載,這點和前面的提到的css 文件恰好相反,因此能夠將它放在頁尾加載。是用戶能看到的頁面內容先加載,js 文件最後加載,這樣會使用戶以爲頁面速度更快。Bigpipe實現一個「barrier」的概念,即當全部的pagelet的內容所有加載好了以後,瀏覽 器再向服務器發送js 的http 請求。能夠在BigPipe.js 中將全部的pagelet 所需的js文件的路徑保存下來,在判斷全部的內容加載完成後統一貫服務器發送請求。

BigPipe 具體實現細節

如上文討論的那樣,具體實現以下:當用戶訪問該頁面 時,在第一個flush 的Response 內容中,返回大部分的HTML 代碼,包括完整的<heaad>標籤,和一個未封閉的<body>,其中<head>標籤中有須要導入的文件的路 徑,如一些公共的css 文件和BigPipe.js 文件,<body>標籤有頁面的主要佈局,第二塊flush 的內容爲一段js腳本,處理BigPipe 對象的生成,以及js 和css 文件的路徑和字符串的映射

var bigPipe = new bigPipe();

bigPipe.setResourceMap({

aaaaa:{

「name」: 「js/list1.js」,

「type」: 「js」,

「src」: 「js/list1.js」

}

);

setResourceMap(json)爲 BigPipe 中的函數,功能是設置文件的映射。」aaaaa」應該是在服務器隨即生成的五位字符串,name表示文件名稱,type 爲文件的類型,能夠是」js」或」css」,」src」爲文件的路徑。在下面的頁面中,就可使用」aaaaa」來替代」js/list1.js」了,減 少了複雜性。接下來flush 的是每個pagelet 的內容了,例如:

<script type=」text/javascript」 >

bigPipe.onPageletArrive({

id:」list1″,

content:」this is list 1 <\/br><img src =\」img13.jpg\」 \/>」,

css:["eeeee"],

js:["aaaaa"],

「resource_map」:{

aaaaa:{

「name」: 「js/list1.js」,

「type」: 「js」,

「src」: 「js/list1.js」

} ,

「eeeee」: {

「name」: 「css/list1.css」,

「type」: 「css」

「src」: 「css/list1.css」

}

}

});

</script>

onPageletArrive(json_arrive) 也是BigPipe 的函數,功能是動態添加頁面的內容和加載pagelet 所需的文件,函數的參數爲json 格式的數據。其參數含義是:「id」用來尋找pagelet 標籤;「 content 」是html 頁面內容,在找到對應的pagelet 的標籤以後,將content 內動態添加到html 頁面中;「css」爲該Pagelet 所需的css 文件,這裏的css 文件可能在以前導入過了;「js」爲該pagelet 所需的js 文件,一樣,有可能在以前的pagelet已經導入過了。在函數實現過程當中,由於js 文件是最後加載的,能夠把這些js 的路徑存入到一個數組當中(去掉重複的),在最後一塊兒加載。resource_map」爲該pagelet 所單獨須要加載的js 和css 文件,一樣也是json 格式的,結構與前面的setResource()中的參數同樣。最後flush 的是

</body>

</html>

即爲最後的標籤。

結論

通過上面的討論,咱們能夠發現,使用BigPipe 技術優化頁面能夠有四個好處:

1. 減小頁面的加載時間

2. 使頁面分步輸出,改善用戶體驗

3. 使頁面結構化,提升可讀性,更加便於維護

4. 每一個pagelet 都是相互獨立的,若是有一個pagelet 的內容不能加載,並不會影響其餘的pagelet 的內容顯示。

同時,BigPipe 是一項比較新的理念, 在去年六月份才由Facebook 的工程師提出,應該說有很大的發展空間。BigPipe 的原理很是簡單,並不會引入不少額外的負擔,適用範圍很廣,容易上手。幾乎全部的網頁均可以採用BigPipe 的理念去進行優化,尤爲對因而有着海量數據和網頁比較大的網站,將會以低成本帶來高回報。通常來說,網站越大,腳本和樣式表越多,瀏覽器版本越舊,網絡環 境越差,優化的結果越可觀。

相關文章
相關標籤/搜索