BigPipe学习研究
1. 技術(shù)背景 FaceBook頁面加載技術(shù)
試想這樣一個(gè)場景,一個(gè)經(jīng)常訪問的網(wǎng)站,每次打開它的頁面都要要花費(fèi)6 秒;同時(shí)另外一個(gè)網(wǎng)站提供了相似的服務(wù),但響應(yīng)時(shí)間只需3 秒,那么你會如何選擇呢?數(shù)據(jù)表明,如果用戶打開一個(gè)網(wǎng)站,等待3~4 秒還沒有任何反應(yīng),他們會變得急躁,焦慮,抱怨,甚至關(guān)閉網(wǎng)頁并且不再訪問,這是非常糟糕的情況。所以,網(wǎng)頁加載的速度十分重要,尤其對于擁有遍布全球的 5億用戶的Facebook(全球最大的社交服務(wù)網(wǎng)站)這樣的大型網(wǎng)站,有著大量并發(fā)請求、海量數(shù)據(jù)等客觀情況,速度就成了必須攻克的難題之一。
2010 年初的時(shí)候,F(xiàn)acebook 的前端性能研究小組開始了他們的優(yōu)化項(xiàng)目,經(jīng)過了六個(gè)月的努力,成功的將個(gè)人空間主頁面加載耗時(shí)由原來的5 秒減少為現(xiàn)在的2.5 秒。這是一個(gè)非常了不起的成就,也給用戶來帶來了很好的體驗(yàn)。在優(yōu)化項(xiàng)目中,工程師提出了一種新的頁面加載技術(shù),稱之為Bigpipe。目前淘寶和 Facebook 面臨的問題非常相似:海量數(shù)據(jù)和頁面過大,如果可以在詳情頁、列表頁中使用bigpipe,或者在webx中集成bigpipe,將會帶來明顯的頁面加載 速度提升。
2. 相關(guān)介紹
2.1 網(wǎng)站前端優(yōu)化的重要性
《高性能網(wǎng)站建設(shè)指南》一書中指出,只有10%~20%的最終用戶響應(yīng)時(shí)間是花費(fèi)在從Web 服務(wù)器獲取HTML 文檔并傳送到瀏覽器中的。如果希望能夠有效地減少頁面的響應(yīng)時(shí)間,就必須關(guān)注剩余的80%~90%的最終用戶體驗(yàn)。做個(gè)比較,如果對后臺業(yè)務(wù)邏輯進(jìn)行優(yōu) 化,效率提高了50%,但最終的頁面響應(yīng)時(shí)間只減少了5%~10%,因?yàn)樗嫉谋戎剌^少。如果對前端進(jìn)行性能優(yōu)化,效率提升50%,則會使最終頁面響應(yīng) 時(shí)間減少40%~45%。這是多么可觀的數(shù)字!另外,前端的性能優(yōu)化一般比業(yè)務(wù)邏輯的優(yōu)化更加容易。所以,前端優(yōu)化投入小,見效快,性價(jià)比極高,需要投入 更多的關(guān)注。
2.2 BigPipe與AJAX
Web2.0的重要特征是網(wǎng)頁顯示大量動態(tài)內(nèi)容,即web2.0注重網(wǎng)頁與用戶的交互。其核心技術(shù)是AJAX,如今所有主流網(wǎng)站都或多或少使用 AJAX。與AJAX類似,BigPipe 實(shí)現(xiàn)了分塊兒的概念,使頁面能夠分步輸出,即每次輸出一部分網(wǎng)頁內(nèi)容。接下來討論BigPipe 與AJAX 的區(qū)別。
簡單的說,BigPipe 比AJAX 有三個(gè)好處:
1. AJAX 的核心是XMLHttpRequest,客戶端需要異步的向服務(wù)器端發(fā)送請求,然后將傳送過來的內(nèi)容動態(tài)添加到網(wǎng)頁上。如此實(shí)現(xiàn)存在一些缺陷,即發(fā)送往返 請求需要耗費(fèi)時(shí)間,而BigPipe 技術(shù)使瀏覽器并不需要發(fā)送XMLHttpRequest 請求,這樣就節(jié)省時(shí)間損耗。
2. 使用AJAX時(shí),瀏覽器和服務(wù)器的工作順序執(zhí)行。服務(wù)器必須等待瀏覽器的請求,這樣就會造成服務(wù)器的空閑。瀏覽器工作時(shí),服務(wù)器在等待,而服務(wù)器工作時(shí), 瀏覽器在等待,這也是一種性能的浪費(fèi)。使用BigPipe,瀏覽器和服務(wù)器可以并行同時(shí)工作,服務(wù)器不需要等待瀏覽器的請求,而是一直處于加載頁面內(nèi)容的 工作階段,這就會使效率得到更大的提高。
3. 減少瀏覽器發(fā)送到請求。對一個(gè)5億用戶的網(wǎng)站來說,減少了使用AJAX額外帶來的請求,會減少服務(wù)器的負(fù)載,同樣會帶來很大的性能提升。
基于以上三點(diǎn),F(xiàn)acebook 在進(jìn)行頁面優(yōu)化時(shí)采用了BigPipe 技術(shù)。目前淘寶主搜索結(jié)果頁中,需要加載類目,相關(guān)搜索,寶貝列表,廣告等內(nèi)容,前端這里使用php 的curl 的批處理來并發(fā)的訪問引擎獲取相應(yīng)的數(shù)據(jù),并進(jìn)行分步輸出。這種模式還是與bigpipe有些不同,這點(diǎn)后面會講到。一般來講,在頁面比較大,而且比較復(fù) 雜,樣式表和腳本比較多的情況下,使用BigPipe 來優(yōu)化輸出頁面是比較合適的。另外非常重要的一點(diǎn),BigPipe 并不改變?yōu)g覽器的結(jié)構(gòu)與網(wǎng)絡(luò)協(xié)議,僅使用JS就可以實(shí)現(xiàn),用戶不需要做任何的設(shè)置,就會看到明顯的訪問時(shí)間縮短。
3 目前的問題
接下來討論現(xiàn)有的瓶頸。面對網(wǎng)頁越來越大的情況,尤其是大量的css 文件和js 文件需要加載,傳統(tǒng)的頁面加載模型很難滿足這樣的需求,直接結(jié)果就是頁面加載速度變慢,這絕不是我們希望看到的。目前的技術(shù)實(shí)現(xiàn)中,用戶提出頁面訪問請求后,頁面的完整加載流程如下:
1. 用戶訪問網(wǎng)頁,瀏覽器發(fā)送一個(gè)HTTP 請求到網(wǎng)絡(luò)服務(wù)器
2. 服務(wù)器解析這個(gè)請求,然后從存儲層去數(shù)據(jù),接著生成一個(gè)html 文件內(nèi)容,并在一個(gè)HTTP Response 中把它傳送給客戶端
3. HTTP response 在網(wǎng)絡(luò)中傳輸
4. 瀏覽器解析這個(gè)Response ,創(chuàng)建一個(gè)DOM 樹,然后下載所需的CSS 和JS文件
5. 下載完CSS 文件后,瀏覽器解析他們并且應(yīng)用在相應(yīng)的內(nèi)容上
6. 下載完JS 后,瀏覽器解析和執(zhí)行他們
圖1.
完整流程見圖1.圖中左側(cè)表示服務(wù)器,右側(cè)表示瀏覽器。瀏覽器先發(fā)送請求,然后服務(wù)器進(jìn)行查找數(shù)據(jù),生成頁面,返回html 代碼,最后瀏覽器進(jìn)行渲染頁面。這種模式有非常明顯的缺陷:流程中的操作有著嚴(yán)格的順序,如果前面的一個(gè)操作沒有執(zhí)行結(jié)束,后面的操作就不能執(zhí)行,即操作 之間是不能重疊。這樣就造成性能的瓶頸:服務(wù)器生成一個(gè)頁面的內(nèi)容時(shí),瀏覽器是空閑的,顯示空白內(nèi)容;而當(dāng)瀏覽器加載渲染頁面內(nèi)容時(shí),服務(wù)器又是空閑的, 時(shí)間與性能的浪費(fèi)由此產(chǎn)生。
圖2.
考慮圖2 中現(xiàn)有的服務(wù)模型,橫軸表示花費(fèi)的時(shí)間。黃色表示在服務(wù)器的生成頁面內(nèi)容的時(shí)間,白色表示網(wǎng)絡(luò)傳輸時(shí)間,藍(lán)色表示在瀏覽器渲染頁面的時(shí)間??梢钥闯觯F(xiàn)有 的模式造成很大的時(shí)間浪費(fèi)。 考慮圖3 中的情況,圖中綠色表示服務(wù)器從春儲層取查數(shù)據(jù)花費(fèi)的時(shí)間,在海量數(shù)據(jù)下,當(dāng)執(zhí)行一條很費(fèi)時(shí)的查詢語句時(shí)(如下圖右側(cè)),服務(wù)器就就阻塞在那 里沒有其他操作,而瀏覽器更是得不到任何反饋。這會造成非常不友好的用戶體驗(yàn),用戶不知道什么原因使他們等待很長時(shí)間。
圖3.
4 BigPipe思想與原理
面對上述問題,我們看下BigPipe的解決辦法。BigPipe提出分塊的概念,即根據(jù)頁面內(nèi)容位置的不同,將整個(gè)頁面分成不同的塊兒– 稱為pagelet。該技術(shù)的設(shè)計(jì)者Changhao Jiang 是研究電子電路的博士,可能從微機(jī)上得到了啟發(fā),將眾多pagelet加載的不同階段像流水線一樣在瀏覽器和服務(wù)器上執(zhí)行,這樣就做到了瀏覽器和服務(wù)器的 并行化,從而達(dá)到重疊服務(wù)器端運(yùn)行時(shí)間和瀏覽器端運(yùn)行時(shí)間的目的。使用BigPipe 不僅可以節(jié)省時(shí)間,使加載的時(shí)間縮短,而且可以同過pagelet的分步輸出,使一部分的頁面內(nèi)容更快的輸出,從而獲得更好的用戶體驗(yàn)。BigPipe 中,用戶提出頁面訪問請求后,頁面的完整加載流程如下:
1. Request parsing:服務(wù)器解析和檢查http request
2. Datafetching:服務(wù)器從存儲層獲取數(shù)據(jù)
3. Markup generation:服務(wù)器生成html 標(biāo)記
4. Network transport : 網(wǎng)絡(luò)傳輸response
5. CSS downloading:瀏覽器下載CSS
6. DOM tree construction and CSS styling:瀏覽器生成DOM 樹,并且使用CSS
7. JavaScript downloading: 瀏覽器下載頁面引用的JS 文件
8. JavaScript execution: 瀏覽器執(zhí)行頁面JS代碼
這個(gè)8 個(gè)流程幾乎與上文中提到現(xiàn)有的模式?jīng)]有區(qū)別,但這整個(gè)流程只是一個(gè)pagelet 的完整流程,而多個(gè)pagelet 的不同操作階段就可以像流水線一樣進(jìn)行執(zhí)行了。
圖4
圖4 中,可以看出BigPipe 對原有的模式進(jìn)行的改進(jìn)。瀏覽器發(fā)送訪問請求,然后瀏覽器分步返回不同的pagelet的內(nèi)容,具體實(shí)現(xiàn)將在后面介紹.考慮圖5中的改進(jìn),BigPipe 打破了原有的順序執(zhí)行,將頁面分成不同的pagelet ,如此一來,所有的pagelet 的執(zhí)行時(shí)間累加起來還是原有的時(shí)間。但是, 通過疊加不同pagelet 的不同階段的執(zhí)行時(shí)間,使總的運(yùn)行時(shí)間大大減少,這就是Bigpipe減少頁面加載時(shí)間的秘密。
FaceBook的頁面被分成了很多不同的pagelets,如圖:
圖5
5 BigPipe實(shí)現(xiàn)原理
了解了BigPipe 的核心思想后,我們討論它的實(shí)現(xiàn)原理。當(dāng)瀏覽器訪問服務(wù)器時(shí),服務(wù)器接受請求并對其進(jìn)行檢查。如果請求有效,服務(wù)器端不做任何的查詢,而是立刻返回一個(gè) http request 給瀏覽器,內(nèi)容是一段html 代碼,包括html<head> 標(biāo)簽和<body> 標(biāo)簽的一部分。<head>標(biāo)簽包括BigPipe 的js文件和css文件,這個(gè)js 文件用來解析后面接收的http response,因?yàn)楹竺鎮(zhèn)鬏數(shù)膬?nèi)容都為js腳本。未封閉的<body>標(biāo)簽中,是顯示頁面的邏輯結(jié)構(gòu)和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 描述了頁面的結(jié)構(gòu),不同的div 標(biāo)簽對應(yīng)不同的pagelet,id 對應(yīng)了pagelet 的名稱。將這個(gè)response 返回給瀏覽器后,服務(wù)器開始對每個(gè)pagelet 的內(nèi)容進(jìn)行查詢,加載,生成。當(dāng)一個(gè)pagelet的內(nèi)容生成好,立刻調(diào)用flush()函數(shù),將其返回給客戶端,傳輸?shù)臄?shù)據(jù)是以json 格式的,包括這個(gè)pagelet 需要的CSS 和JS,以及html 內(nèi)容和一些元數(shù)據(jù)。例如:
<script type=”text/javascript”>
big_pipe.onPageletArrive(
{id:”pagelet_composer”,
content:”<HTML>”,
css:”[..]“,
js:”[..]“,
…}
);
</script>
其中”content”表示這個(gè)pagelet 的內(nèi)容,是html 源碼,特殊字符如“”/需要進(jìn)行轉(zhuǎn)義;”id”表示content要顯示的位置,即為對應(yīng)的pagelet 的id標(biāo)簽;”css”表示需要下載的CSS 資源的路徑;”js”表示需要下載的JS 腳本的路徑。為了避免文件路徑過長,所以在前面需要對css 和js 文件的路徑進(jìn)行轉(zhuǎn)換,轉(zhuǎn)換后為5 位字符串:不同的pagelet 可能會加載同一個(gè)css 或js 文件,所以要避免重復(fù)下載。
雖然每個(gè)pagelet 都有要加載的js 文件,但是所有的js 文件都是在最后加載,這樣有利于加快頁面加載速度??蛻舳?,當(dāng)通過調(diào)用“onPageletArrive(json)”函數(shù),第一次影響傳輸?shù)腏S腳本中 的函數(shù)解析了傳入的json 數(shù)據(jù),接著下載需要的CSS,然后把html 內(nèi)容顯示到響應(yīng)的DIV 標(biāo)簽位置上。多個(gè)pagelets 的CSS文件可以同時(shí)下載,CSS 下載完成的pagelet 先顯示。
在BigPipe 中,js 被給予了比CSS 和content 更低的優(yōu)先級。這樣, 只有當(dāng)所有的pagelets 都顯示了,BigPipe 才開始去下載JS 文件。所有的JS 文件都下載完成后,Pagelets的JS初始化代碼開始執(zhí)行,按照下載完成時(shí)間的先后順序。在這個(gè)高度并行的系統(tǒng)中,幾個(gè)的pagelet 所要執(zhí)行的不同的階段可以同時(shí)執(zhí)行。例如,瀏覽器可以給兩個(gè)pagelets 下載CSS 資源,同時(shí)瀏覽器可以渲染另外一個(gè)pagelet 的內(nèi)容,同時(shí)服務(wù)器仍然在為另一個(gè)pagelet 生成html源碼。從用戶的角度看來,頁面時(shí)逐步呈現(xiàn)的。初始的頁面顯示的更快,可以有效減短用戶感覺到的延遲。
6 BigPipe實(shí)現(xiàn)問題討論
6.1 服務(wù)器端的并行化
理想情況下,服務(wù)器端的實(shí)現(xiàn)是并行處理不同的pagelet 的內(nèi)容,這樣可以提升性能。服務(wù)器并發(fā)處理多個(gè)pagelet 的內(nèi)容時(shí),一個(gè)pagelet 內(nèi)容生成好了,立刻將其flush 給瀏覽器。但是PHP 是不支持線程,所以服務(wù)器無法利用多線程的概念去并發(fā)的加載多個(gè)pagelet 的內(nèi)容。對于小型網(wǎng)站來說,使用串行的加載pagelet 的內(nèi)容就已經(jīng)可以達(dá)到優(yōu)化的要求了。對于大型網(wǎng)站,為了達(dá)到更快的速度,服務(wù)器端可以選擇并發(fā)的獨(dú)立不同的pagelet 的內(nèi)容,具體實(shí)現(xiàn)有以下幾種方式:
1.java 多線程。后臺邏輯使用java,可以使用java 的多線程機(jī)制去同時(shí)加載不同的pagelet 的內(nèi)容,加載完成后加頁面內(nèi)容返回給瀏覽器。在最后的引用部分可以看到網(wǎng)上用java多線程實(shí)現(xiàn)的例子。
2.使用PHP實(shí)現(xiàn)。PHP 不支持線程,無法像java 使用多線程的機(jī)制來并發(fā)處理不同pagelet 的內(nèi)容。但是,F(xiàn)acebook 和淘寶主搜索的業(yè)務(wù)邏輯是用PHP 實(shí)現(xiàn)的,所以我們必須考慮如何在PHP下完成并發(fā)處理。PHP 擴(kuò)展中有curl 模塊,可以在該模塊中curl_multi_fetch()函數(shù)進(jìn)行批處理請求,把本來應(yīng)該串行的請求訪問并發(fā)的執(zhí)行。可以這樣寫:
| 1234567891011121314151617181920212223 | do {$mrc = curl_multi_exec($mh, $active);}while($mrc==CURLM_CALL_MULTI_PERFORM);while ($active && $mrc == CURLM_OK){if (curl_multi_select($mh)!= -1){do {$mrc = curl_multi_exec($mh,$active);}while($mrc==CURLM_CALL_MULTI_PERFORM);}} |
但是會碰到一個(gè)問題,多個(gè)請求是同時(shí)返回結(jié)果的。當(dāng)所有的pagelet 的頁面請求所消耗的時(shí)間差不多時(shí),可以達(dá)到很好的性能,但是當(dāng)有的消耗時(shí)間很長(執(zhí)行一條復(fù)雜的查詢)的情況下,批處理就會阻塞在那里,等每個(gè)請求都返回 結(jié)果了才結(jié)束。而在這段時(shí)間致服務(wù)器會阻塞在那里不返回任何內(nèi)容,而瀏覽器更是沒有響應(yīng),這樣就違背了BigPipe 的原理。另外一種實(shí)現(xiàn)方法是使用stream_select函數(shù)。跟上一種方法類似,不過可以使用PHP5中新增的 stream_socket_client()函數(shù)鏈接而不是之前PHP函數(shù)中的fsocketopen()函數(shù)。
| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 | while (count($sockets)) {$read = $write = $sockets;$n = stream_select($read,$write, $e, $timeout);if ($n > 0) {foreach ($read as $r) {$id = array_search($r, $sockets);$data = fread($r, 8192);if (strlen($data) == 0) {fclose($r);unset ($sockets[$id]);}else {$retdata[$id] .= $data;}}$retdata[$id] = preg_replace('/^HTTP(.*?)\r\n\r\n/is',<em>, $retdata[$id]);</em>foreach ($write as $w) {if (!is_resource($w))continue;$id = array_search($w, $sockets);fwrite($w, "GET /" . $url[$id] . "HTTP/1.0\r\nHost: " . $hosts[$id] ."\r\n\r\n");$status[$id] = 1;}}else {break;}} |
這樣實(shí)現(xiàn)也可以做到服務(wù)器的并發(fā)訪問,但是會碰到和上一種方法同樣的問題:服務(wù)器的阻塞問題。所以,可以采用另一種方法,用多進(jìn)程模擬多線程。使用 PHP 的擴(kuò)展模塊pctnl模塊中的pcntl_fork()函數(shù)來生成子進(jìn)程, 用不同的子進(jìn)程去處理不同的pagelet 的頁面內(nèi)容。如果子進(jìn)程返回內(nèi)容,則返回給瀏覽器。或者,修改curl模塊。使其可以支持回調(diào)函數(shù),當(dāng)并發(fā)請求中一個(gè)請求完成時(shí),立刻調(diào)用回調(diào)函數(shù)。這兩 種方法目前還在探索中。
6.2 直接調(diào)用flush函數(shù)輸出
到這里,可能會有這樣的疑問,為什服務(wù)器不直接把生成好的HTML 內(nèi)容分部flush() 返回給客戶端,而是使用json 格式傳遞,然后用js 解析呢?這不是多此一舉么?實(shí)際上,這也是目前主搜索前端使用的方法。我們看看使用BigPipe方式的兩大好處:
(1) 如果直接調(diào)用flush()函數(shù)輸出html 源碼,當(dāng)模塊較多的情況,模塊間必須按順序加載,在html 前面的模塊必須先加載完,后面的才能加載,這樣也就沒辦法每個(gè)模塊同時(shí)顯示一些內(nèi)容。例如下面的html:
上面3 個(gè)div 分別代表3 個(gè)模塊,如果直接分部輸出html ,服務(wù)器端必須先加載完畢div1 模塊中的內(nèi)容并flush 出去后,才能繼續(xù)加載div2的內(nèi)容,如果flush 順序不一樣,輸出的html 結(jié)構(gòu)肯定就會出問題,這樣就導(dǎo)致前臺頁面沒辦法同時(shí)顯示3 個(gè)loading。因?yàn)檫@樣flush 必須要有先后順序。而如果采用JS 的話,可以前臺顯示3 個(gè)loading,而且不需要關(guān)心到底哪個(gè)模塊先加載完,這樣還能發(fā)揮后臺多線程處理數(shù)據(jù)的優(yōu)勢。
(2)使用JS 這種方式可以是頁面結(jié)構(gòu)更加清晰,管理更加方便。同時(shí)做到了頁面邏輯結(jié)構(gòu)和數(shù)據(jù)解耦,首先返回的是頁面的結(jié)構(gòu),接著不斷地返回js腳本,然后動態(tài)添加頁面內(nèi)容,而不是所有完整的html 源碼一起輸出,增加了可維護(hù)性。
6.3 訪問者是爬蟲或者訪問者瀏覽器禁止使用JS的情況
我們知道BigPipe 使用js 腳本加載頁面,那么當(dāng)用戶在瀏覽器里設(shè)置禁止使用js 腳本(雖然人數(shù)很少),就會造成加載頁面失敗,這同樣是非常不好的用戶體驗(yàn)。對搜索引擎的爬蟲來講,同樣會遇到類似的問題。解決辦法是當(dāng)用戶發(fā)送訪問請求 時(shí),服務(wù)器端檢測user-agent 和客戶端是否支持js 腳本。如果user-agent 顯示是一個(gè)搜索引擎爬蟲或者客戶端不支持js,就不使用BigPipe ,而用原有的模式,從而解決問題。
6.4 對SEO的影響
這是一個(gè)必須考慮的問題,如今是搜索引擎的時(shí)代,如果網(wǎng)頁對搜索引擎不友好,或者使搜索引擎很難識別內(nèi)容,那么會降低網(wǎng)頁在搜索引擎中的排名,直接 減少網(wǎng)站的訪問次數(shù)。在BigPipe 中,頁面的內(nèi)容都是動態(tài)添加的,所以可能會使搜索引擎無法識別。但是正如前面所說,在服務(wù)器端首先要根據(jù)user-agent 判斷客戶端是否是搜索引擎的爬蟲,如果是的話,則轉(zhuǎn)化為原有的模式,而不是動態(tài)添加。這樣就解決了對搜索引擎的不友好。
6.5 融合其他技術(shù)
除了使用BigPipe,F(xiàn)acebook的頁面加載技術(shù)還融合了其他的頁面優(yōu)化技術(shù),具體如下:
6.5.1 資源文件的G-zip壓縮
這是非常重要的技術(shù),使用G-zip 對css 和js 文件壓縮可以使大小減少70%,這是多么誘人的數(shù)字!在網(wǎng)絡(luò)傳輸?shù)奈募?,主要就是樣式表和腳本文件。如此可以大大減小傳輸?shù)膬?nèi)容,使頁面加載速度變得更 快。具體實(shí)現(xiàn)可以借助服務(wù)器來進(jìn)行,例如Apache,使用mod_deflate 模塊來完成具體配置為: AddOutputFilterByType DEFLATE text/html text/css application/xjavascript
6.5.2 將js文件進(jìn)行了精簡
對js 文件進(jìn)行精簡,可以從代碼中移除不必要的字符,注釋以及空行以減小js 文件的大小,從而改善加載的頁面的時(shí)間。精簡js 腳本的工具可以使用JSMin,使用精簡后的腳本的大小會減少20%左右。這也是一個(gè)很大的提升。
6.5.3 將css和js文件進(jìn)行合并
這是前端優(yōu)化的一項(xiàng)原則,將多個(gè)樣式表和js 文件進(jìn)行合并,這樣的話,將會減少http 的請求個(gè)數(shù)。對于上億用戶的網(wǎng)站來說,這也會帶來性能的提升,大約會減少5%左右的時(shí)間損耗。
6.5.4 使用外部JS和CSS
同樣是前端優(yōu)化的一項(xiàng)原則。純粹就速度來言,使用內(nèi)聯(lián)的js 和css 速度要更快,因?yàn)闇p少了http 請求。但是,使用外部的文件更有利于文件的復(fù)用,這與面向?qū)ο缶幊痰母拍詈芟?。更為重要的是,雖然在第一次的加載速度慢一點(diǎn),但css 文件和js腳本是可以被瀏覽器緩存。即之后用戶的多次訪問中,使用外部的js 和css 將會將會更好的提升速度。
6.5.5 將樣式表放在頂部
和上面內(nèi)容相似,這也是一種規(guī)范,將html 內(nèi)容所需的css 文件放在首部加載是非常重要的。如果放在頁面尾部,雖然會使頁面內(nèi)容更快的加載(因?yàn)閷⒓虞dcss 文件的時(shí)間放在最后,從而使頁面內(nèi)容先顯示出來),但是這樣的內(nèi)容是沒有使用樣式表的,在css 文件加載進(jìn)來后,瀏覽器會對其使用樣式表,即再次改變頁面的內(nèi)容和樣式,稱之為“無樣式內(nèi)容的閃爍”,這對于用戶來說當(dāng)然是不友好的。實(shí)現(xiàn)的時(shí)候?qū)ss 文件放在<head>標(biāo)簽中即可。
6.5.6 將腳本放在底部實(shí)現(xiàn)“barrier”
支持頁面動態(tài)內(nèi)容的Js 腳本對于頁面的加載并沒有什么作用,把它放在頂部加載只會使頁面更慢的加載,這點(diǎn)和前面的提到的css 文件剛好相反,所以可以將它放在頁尾加載。是用戶能看到的頁面內(nèi)容先加載,js 文件最后加載,這樣會使用戶覺得頁面速度更快。Bigpipe實(shí)現(xiàn)一個(gè)“barrier”的概念,即當(dāng)所有的pagelet的內(nèi)容全部加載好了之后,瀏覽 器再向服務(wù)器發(fā)送js 的http 請求??梢栽贐igPipe.js 中將所有的pagelet 所需的js文件的路徑保存下來,在判斷所有的內(nèi)容加載完成后統(tǒng)一向服務(wù)器發(fā)送請求。
7 BigPipe具體實(shí)現(xiàn)細(xì)節(jié)
如上文討論的那樣,具體實(shí)現(xiàn)如下:當(dāng)用戶訪問該頁面時(shí),在第一個(gè)flush 的Response 內(nèi)容中,返回大部分的HTML 代碼,包括完整的<heaad>標(biāo)簽,和一個(gè)未封閉的<body>,其中<head>標(biāo)簽中有需要導(dǎo)入的文件的路 徑,如一些公共的css 文件和BigPipe.js 文件,<body>標(biāo)簽有頁面的主要布局,第二塊flush 的內(nèi)容為一段js腳本,處理BigPipe 對象的生成,以及js 和css 文件的路徑和字符串的映射
var bigPipe = new bigPipe();
bigPipe.setResourceMap({
aaaaa:{
“name”: “js/list1.js”,
“type”: “js”,
“src”: “js/list1.js”
}
);
setResourceMap(json)為BigPipe 中的函數(shù),功能是設(shè)置文件的映射?!盿aaaa”應(yīng)該是在服務(wù)器隨即生成的五位字符串,name表示文件名稱,type 為文件的類型,可以是”js”或”css”,”src”為文件的路徑。在下面的頁面中,就可以使用”aaaaa”來替代”js/list1.js”了,減 少了復(fù)雜性。接下來flush 的是每一個(gè)pagelet 的內(nèi)容了,例如:
<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 的函數(shù),功能是動態(tài)添加頁面的內(nèi)容和加載pagelet 所需的文件,函數(shù)的參數(shù)為json 格式的數(shù)據(jù)。其參數(shù)含義是:“id”用來尋找pagelet 標(biāo)簽;“ content ”是html 頁面內(nèi)容,在找到對應(yīng)的pagelet 的標(biāo)簽之后,將content 內(nèi)動態(tài)添加到html 頁面中;“css”為該P(yáng)agelet 所需的css 文件,這里的css 文件可能在之前導(dǎo)入過了;“js”為該pagelet 所需的js 文件,同樣,有可能在之前的pagelet已經(jīng)導(dǎo)入過了。在函數(shù)實(shí)現(xiàn)過程中,因?yàn)閖s 文件是最后加載的,可以把這些js 的路徑存入到一個(gè)數(shù)組當(dāng)中(去掉重復(fù)的),在最后一起加載。resource_map”為該pagelet 所單獨(dú)需要加載的js 和css 文件,同樣也是json 格式的,結(jié)構(gòu)與前面的setResource()中的參數(shù)一樣。最后flush 的是
</body>
</html>
即為最后的標(biāo)簽。
8 結(jié)論
經(jīng)過上面的討論,我們可以發(fā)現(xiàn),使用BigPipe 技術(shù)優(yōu)化頁面可以有四個(gè)好處:
1. 減少頁面的加載時(shí)間
2. 使頁面分步輸出,改善用戶體驗(yàn)
3. 使頁面結(jié)構(gòu)化,提高可讀性,更加便于維護(hù)
4. 每個(gè)pagelet 都是相互獨(dú)立的,如果有一個(gè)pagelet 的內(nèi)容不能加載,并不會影響其他的pagelet 的內(nèi)容顯示。
同時(shí),BigPipe 是一項(xiàng)比較新的理念, 在去年六月份才由Facebook 的工程師提出,應(yīng)該說有很大的發(fā)展空間。BigPipe 的原理非常簡單,并不會引入很多額外的負(fù)擔(dān),適用范圍很廣,容易上手。幾乎所有的網(wǎng)頁都可以采用BigPipe 的理念去進(jìn)行優(yōu)化,尤其對于是有著海量數(shù)據(jù)和網(wǎng)頁比較大的網(wǎng)站,將會以低成本帶來高回報(bào)。一般來講,網(wǎng)站越大,腳本和樣式表越多,瀏覽器版本越舊,網(wǎng)絡(luò)環(huán) 境越差,優(yōu)化的結(jié)果越可觀。
9 引用與參考資料
1.作者的博客:http://www.facebook.com/note.php?note_id=389414033919
2.bigpipe技術(shù)的ppt:http://twork.taobao.net/books/237
3.bigpipe的java實(shí)現(xiàn):http://codemonkeyism.com/facebook-bigpipe-java/
4.一篇介紹bigpipe的文章:http://www.54chen.com/architecture/rose-pipe-http-54chen.html
5.另一篇挺有用的文章:http://www.cnblogs.com/BearsTaR/archive/2010/06/18/facebook_html_chunk.html
6.人人網(wǎng)類似bigpipe的技術(shù)–rosepipe:http://www.54chen.com/architecture/rose-open-source-portal-framework.html
7.《高性能網(wǎng)站建設(shè)指南》by Steve Souder, Copyright 2007 Steve Sounder, 978-0-596- 52930-7
chaunceyhao
總結(jié)
以上是生活随笔為你收集整理的BigPipe学习研究的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Toolbar自定义布局
- 下一篇: Tomcat简介