HTTP協議中chunk的應用場景?

rt, thanks.


著名的 Yahoo Web 開發 14 條軍規的第一條就是「減少你的 Http 請求」。當然這只是一個籠統的說法,或者是給我們指明一條優化的總則。畢竟我們要在「頁面信息規模」和「Http 請求數」上做到一個取捨。不能因為「要減少 Http 請求數」而犧牲了用戶體驗。終歸我們是要將信息通過 HTML 頁面傳達給用戶的。

簡單的說,如果我們只想簡單的達到把信息傳遞給用戶的目的。那我們可以將信息直接輸出到瀏覽器上,可能一個 Http 請求就解決了問題。但這樣的信息即便到達了用戶的瀏覽器,用戶也很難接受。白底黑子,亂呼呼的一堆,你想誰會喜歡看?所以我們在為用戶傳達信息的同時,我們需要載入 CSS、圖片、js腳本等,通過各種複雜而生動的效果,讓我們想要傳達的信息更容易的為用戶所接受。而我們要為此付出的代價就是「增加我們的 Http 請求數」。

我們需要更好的用戶體驗,讓用戶看到他們更容易接受的頁面,這使得我們不斷的增加了我們的 Http 請求數,這與我們的「軍規」背道而馳,難道就沒有一個「兩全其美」的辦法嗎?當然是有的。

首先,我們可以充分利用瀏覽器緩存。把 CSS、js腳本和圖片等靜態文件緩存起來。當瀏覽器再次載入這些資源的時候,我們可以方便的從瀏覽器緩存中取得這些內容。從而大大減少我們發起的 Http 請求。這是一個普遍為大家所熟知而又簡單有效的辦法。但是請注意,瀏覽器緩存只是在我們再次請求同樣的資源時才生效。我們暫且稱之為「第二次請求優化」,而這無法解決「首次載入」時的問題。因為我們第一次去請求這些原本瀏覽器沒有緩存的資源時,我們還是要老老實實的發送「Http 請求」的。

針對我們要研究的「首次載入優化」問題。我們在這裡先做一個假設。假設我們要提供一篇文章給用戶。這篇文章分為題目、文章簡介、章節索引、章節內容四部分。當然我們為了文章的美觀少不了要寫 CSS 樣式、準備一些插圖和通過一些 JS 腳本控制我們的文章結構。那麼當用戶發起請求,向我們「索要」這篇文章時,我們如何做到快速響應,以避免用戶等得不耐煩呢?

我們要展現給用戶的文章是固定的。有題目、文章簡介、章節索引、章節內容四部分。從用戶發起請求想要閱讀這篇文章,到我們提供文章給用戶,為了更為直觀的理解這個過程,我們將他們以慢動作的形式來展現。假設用戶發起的想要閱讀這篇文章的請求經過 1 秒鐘到達我們的伺服器。我們的伺服器接收到這個請求,從資料庫或是其他存儲器中取得這篇文章,並按照題目、文章簡介、章節索引、章節內容的結構組裝好,這個過程花費了 4 秒鐘,然後我們將這篇文章發送給用戶用了 2 秒鐘。用戶從想要看這篇文章到看到了這篇文章,一共花費了 7 秒鐘(當然沒那麼誇張,因為我們的伺服器響應和網路傳輸基本都在毫秒級,我們只是將這個過程以「慢鏡頭」的形式在播放)。

這 7 秒鐘意味著什麼?意味著用戶在點擊滑鼠表達想要閱讀這篇文章到用戶讀到這篇文章前,他要獃獃的面對一張白花花的屏幕 7 秒鐘。會有一部分用戶因為不耐煩而關閉頁面,因為沒有幾個人喜歡對著白屏發獃。

而如何在不犧牲用戶體驗(該載入的 CSS 樣式、文章插圖和 JS 腳本文件一個都不能少)的前提下減少用戶的等待時間,就是我們需要解決的問題關鍵。解決這個問題比較通用的做法就是「分部載入」。就是在用戶發出閱讀請求的時候,並不等文章整體載入完成後再呈現給用戶,而是將他們「打碎」。比如我們可以現將文章的題目呈現給用戶,接著是文章簡介,然後是章節索引,最後呈現章節內容。通過這樣「打碎」文章結構,將文章一部分一部分的呈現給用戶,我們也就「打碎」了用戶的等待時間(用戶不再等待 7 秒鐘去看整篇文章,而是等上 1 秒鐘就會看到文章題目,再等上 1 秒鐘看到文章簡介......通過「打碎」等待時間讓用戶有一種「很快響應」的錯覺)。

寫到這,熟悉 web 開發的讀者可能會想到「用 Ajax 不就可以了嗎!」。沒錯,Ajax 技術完全可以實現這種「分部載入」以提升用戶體驗,「減少」(並不是真正意義上的減少)響應延遲時間。我們可以在用戶請求閱讀這篇文章的時候,快速的載入一個體積較小的頁面,頁面中含有一些 JavaScript 語句,通過這些語句分別發送一些 Ajax 請求,這些請求有的去取文章題目,有的去取文章簡介......並將取到的內容呈現在頁面上,以減少用戶一次性等待響應的時間。Ajax 是個不錯的辦法,但是想想我們的「軍規」,我們要「減少 Http 請求數」!,而 Ajax 在提升用戶體驗,減少用戶等待的同時最大的弊病就是增加了大量的「Http 請求次數」。

除了 Ajax 技術,還有別的辦法嗎?答案是肯定的。接下來就該是我們的 「Chunk 技術」 出場的時間了。Chunk 並不是什麼新技術,而是 Http 協議的一個編碼方式。簡單的解釋就是「伺服器生成HTTP回應是無法確定消息大小的,這時用Content-Length就無法事先寫入長度,而需要實時生成消息長度,這時伺服器一般採用Chunked編碼。 在進行Chunked編碼傳輸時,在回復消息的頭部有transfer-coding並定為Chunked,表示將用Chunked編碼傳輸內容。」我們可以通過 FireBug 等調試工具查看伺服器的 Http 響應頭,看到有 transfer-coding 並且值為 Chunked 的,就表明採用了 Chunk 編碼。

那麼 Chunk 和我們所討論的「分部載入」有什麼關係呢?細心的讀者可能已經看出來了,Http 的 Chunk 編碼實際上就是一種將 Http 響應分部傳送的方法。一個Http響應可以有若干個Chunk組成,由一個標明長度為0的chunk結束,每個Chunk有兩部分組成,第一部分是該Chunk的長度和長度單位(一般不 寫),第二部分就是指定長度的內容。而當瀏覽器接收到伺服器的響應並判定響應是 Chunk 編碼的,則瀏覽器可以在每接收一個 Chunk 段完畢時先行渲染這個 Chunk 段所攜帶的內容。這樣就實現了「分部載入」的目的。

如何應用 Chunk 技術來進行「分部載入」呢?我們以一段 PHP 代碼為例。

& $content = array("我是文章題目", "我是文章簡介", "我是章節索引", "我是章節內容",);

foreach ($content as $c) {

echo "&

$c&";

sleep(1); //我們這裡故意放慢節奏,等待一秒

}

?&>

假設我們想伺服器請求一篇文章的時候由上述 PHP 腳本來響應這次請求。你會發現,你大概需要等待 4 秒鐘後才會看到文章的全部內容(文章題目、文章簡介、章節索引和章節內容)。我們再來看下面的這種通過 Chunk 來「分部載入」的代碼實現:

& $content = array("我是文章題目", "我是文章簡介", "我是章節索引", "我是章節內容",);

$buffer_size = 4096;

foreach ($content as $c) {

echo str_pad( "&

$c&", $buffer_size);

ob_flush();

flush();

sleep(1); //我們這裡故意放慢節奏,等待一秒

}

?&>

這次我們再來試試這段 PHP 代碼作為響應文章請求時的效果。你會發現,你先看到了文章題目,1 秒鐘後瀏覽器為你呈現了文章簡介,又 1 秒鐘後是章節索引,再 1 秒鐘後章節內容也出來了。「分部載入」的效果出來了,你再也不同等待 4 秒鐘了!

簡單的解釋一些上面的代碼。PHP 本身是支持這種通過 Http Chunk 編碼來響應請求的。我們每 echo 一段要輸出的內容後,調用 ob_flush 和 flush 兩個方法來清空緩存,這樣 echo 的內容會直接發送給瀏覽器,達到「分部」完成響應的目的。

最後在說明一下「分部載入」時的 echo 為什麼使用了 str_pad 方法。由於 PHP 本身緩存大小(php.ini 中的 ob_buffering),不同 web 伺服器(Nginx、Apache)和瀏覽器不同的關係,當我們 echo 的內容過小時,即使調用了 ob_flush 和 flush 方法清空輸出緩存,echo 的內容也未必會即時發送,這是我們就需要調用 str_pad 方法來讓我們 echo 的內容「增肥」以達到佔滿緩存的目的。

對於實現「分部載入」,Ajax 和 Chunk 都可以達到同樣的效果。但是由於 Chunk 不會產生額外的 Http 請求,所以更為輕盈(畢竟發起 Http 請求也是時分昂貴的!)。

Facebook 為我們大家所熟知(雖然我們無法使用),其用戶個人主頁信息量之大,結構之複雜也可見一斑。Facebook為了減少頁面的響應時間,發明了一種他們稱之為「BigPipe」的技術,這個技術的核心原理就是 Http 的 Chunk 編碼。據Facebook的測試表明「BigPipe」技術使其頁面響應時間減少了約 50% (FireFox3.6除外,用 FireFox3.6的響應時間減少約22%),可見很好的利用 Http Chunk 技術,可以大大提高我們 Http 的響應速度,有興趣的同學可以深入的研究一下,也歡迎大家與我探討。

最後放上 Chunk 技術和 BigPipe 技術的相關文章,剩下的大家問問 Google,自學一下!

Chunk 技術:《flush讓頁面分塊,逐步呈現》

Facebook BigPipe 技術 : 《名站技術分析 — facebook奇特的頁面載入技術》

《Facebook創新之BigPipe:優化頁面載入時間》

《BigPipe: Pipelining web pages for high performance》

轉發自:http://leenjewel.blog.163.com/blog/static/6019379220111144370750/


包頭header中的,Transfer-Encoding: chunked 表示輸出的內容長度不能確定,普通的靜態頁面、圖片之類的基本上都用不到這個。

但動態頁面就有可能會用到,但我也注意到大部分asp,php,http://asp.net動態頁面輸出的時候大部分還是使用Content-Length,沒有使用Transfer-Encoding: chunked。

不過如果結合:Content-Encoding: gzip 使用的時候,Transfer-Encoding: chunked還是比較有用的。

回答的可能不是想要的結果,見諒~


推薦閱讀:

如何評價英特爾 2017 年 8 月 21 日發布的第8代酷睿處理器?
王垠比我們強在何處,為什麼他能找到ABC女朋友?
把电脑壁纸设置为纯黑,会不会更省电?
將硬碟上一個程序分成幾段載入內存,是多進程還是多線程?

TAG:程序員 | 計算機 | 計算機網路 | HTTP |