HTTP/2 與 WEB 性能優(yōu)化(二)
在「HTTP/2 與 WEB 性能優(yōu)化(一)」這篇博客中,我主要寫了 HTTP/2 中的 Server Push 給 WEB 性能優(yōu)化帶來的便利,今天繼續(xù)來聊一聊 HTTP/2 其他方面的改變。
我們知道,HTTP/2 并沒有改動 HTTP/1 的語義部分,例如請求方法、響應(yīng)狀態(tài)碼、URI 以及頭部字段等核心概念依舊存在。HTTP/2 最大的變化是重新定義了格式化和傳輸數(shù)據(jù)的方式,這是通過在高層 HTTP API 和低層 TCP 連接中引入二進制分幀層來實現(xiàn)。這樣改動的好處是原來的 WEB 應(yīng)用完全不用修改,就能享受到協(xié)議升級帶來的收益。
HTTP/2 的連接
HTTP/1 的請求和響應(yīng)報文,都是由起始行、首部和實體正文(可選)組成,各部分之間以文本換行符分隔。而 HTTP/2 將請求和響應(yīng)數(shù)據(jù)分割為更小的幀,并對它們采用二進制編碼。下面這幅圖中的 Binary Framing 就是新增的二進制分幀層:
先來看看這幾個概念:
幀(Frame):HTTP/2 數(shù)據(jù)通信的最小單位。幀用來承載特定類型的數(shù)據(jù),如 HTTP 首部、負荷;或者用來實現(xiàn)特定功能,例如打開、關(guān)閉流。每個幀都包含幀首部,其中會標識出當(dāng)前幀所屬的流;
消息(Message):指 HTTP/2 中邏輯上的 HTTP 消息。例如請求和響應(yīng)等,消息由一個或多個幀組成;
流(Stream):存在于連接中的一個虛擬通道。流可以承載雙向消息,每個流都有一個唯一的整數(shù) ID;
連接(Connection):與 HTTP/1 相同,都是指對應(yīng)的 TCP 連接;
在 HTTP/2 中,同域名下所有通信都在單個連接上完成,這個連接可以承載任意數(shù)量的雙向數(shù)據(jù)流。每個數(shù)據(jù)流都以消息的形式發(fā)送,而消息又由一個或多個幀組成。多個幀之間可以亂序發(fā)送,因為根據(jù)幀首部的流標識可以重新組裝。下面有一幅圖說明幀、消息、流和連接的關(guān)系:
TCP 協(xié)議本身更適合用來長時間傳輸大數(shù)據(jù),這樣它的穩(wěn)定和可靠性才能顯露出來。HTTP/1 時代太多短而小的 TCP 連接,反而更多地將 TCP 的缺點給暴露出來了。
HTTP/1 的連接
在 HTTP/1 中,每一個請求和響應(yīng)都要占用一個 TCP 連接,盡管有 Keep-Alive 機制可以復(fù)用,但在每個連接上同時只能有一個請求 / 響應(yīng),這意味著完成響應(yīng)之前,這個連接不能用于其他請求(怎么判斷響應(yīng)是否結(jié)束,可以看這里)。如果瀏覽器需要向同一個域名發(fā)送多個請求,需要在本地維護一個 FIFO 隊列,完成一個再發(fā)送下一個。這樣,從服務(wù)端完成請求開始回傳,到收到下一個請求之間的這段時間,服務(wù)端處于空閑狀態(tài)。
后來,人們提出了 HTTP 管道(HTTP Pipelining)的概念,試圖把本地的 FIFO 隊列挪到服務(wù)端。它的原理是這樣的:瀏覽器一股腦把請求都發(fā)給服務(wù)端,然后等著就可以了。這樣服務(wù)端就可以在處理完一個請求后,馬上處理下一個,不會有空閑了。甚至服務(wù)端還可以利用多線程并行處理多個請求??上В驗? HTTP/1 不支持多路復(fù)用,這個方案有幾個棘手的問題:
服務(wù)端收到多個管道請求后,需要按接收順序逐個響應(yīng)。如果恰好第一個請求特別慢,后續(xù)所有響應(yīng)都會跟著被阻塞。這種情況通常被稱之為「隊首阻塞(Head-of-Line Blocking)」;
服務(wù)端為了保證按順序回傳,通常需要緩存多個響應(yīng),從而占用更多的服務(wù)端資源,也更容易被人攻擊;
瀏覽器連續(xù)發(fā)送多個請求后,等待響應(yīng)這段時間內(nèi)如果遇上網(wǎng)絡(luò)異常導(dǎo)致連接被斷開,無法得知服務(wù)端處理情況,如果全部重試可能會造成服務(wù)端重復(fù)處理;
另外,服務(wù)端和瀏覽器之間的中間代理設(shè)備也不一定支持 HTTP 管道,這給管道技術(shù)的普及引入了更多復(fù)雜性;
基于這些原因,HTTP 管道技術(shù)無法大規(guī)模使用,我們需要尋找其他方案。實際上,在 HTTP/1 時代,連接數(shù)優(yōu)化不外乎兩個方面:開源和節(jié)流。
開源
這里說的開源,當(dāng)然不是「Open Source」那個開源。既然一個 TCP 連接同時只能處理一個 HTTP 消息,那多開幾條 TCP 連接不就解決這個問題了。是的,瀏覽器確實是這么做的,HTTP/1.1 初始版本中允許瀏覽器針對同一個域名同時創(chuàng)建兩個連接,在修訂版(rfc7230)中更是去掉了這個限制。實際上,現(xiàn)代瀏覽器一般允許同域名并發(fā) 6~8 個連接。這個數(shù)字為什么不能更大呢?實際上這是出于公平性的考慮,每個連接對于服務(wù)端來說都會帶來一定開銷,如果瀏覽器不加以限制,一個性能好帶寬足的終端就可能耗盡服務(wù)端所有資源,造成其他人無法使用。
但是,現(xiàn)在包含幾十個 CSS、JSS,幾百張圖片的頁面大有所在。為了進一步榨干瀏覽器,開更多的源,往往我們還會對靜態(tài)資源做域名散列,將頁面靜態(tài)資源分散在多個子域下加載。多域名能提高并發(fā)連接數(shù),也會帶來很多問題,例如:
如果同一資源在不同頁面被散列到不同子域下,會導(dǎo)致無法利用之前的 HTTP 緩存;
每個域名的第一個連接都要經(jīng)歷 DNS 解析的過程,這在移動端可能需要耗費幾百毫秒;
更多的并發(fā)連接 + Keep-Alive 機制,會顯著增加服務(wù)端和客戶端的負擔(dān);
這里稍微吐槽下:本地 TCP 連接和本地端口也是一種資源,為了做 WEB 性能優(yōu)化,開更多的域名讓瀏覽器創(chuàng)建更多的并發(fā)連接,是很霸道和不公平的做法。
另外,HTTP/1 協(xié)議頭部使用純文本格式,沒有任何壓縮,且包含很多冗余信息(例如 Cookie、UserAgent 每次都會攜帶),所以一個頁面的請求數(shù)越多,頭部帶來的額外開銷就越大。我們一般會用短小且獨立的域名來托管靜態(tài)資源,就是為了減小這個開銷(域名越短請求頭起始行的 URI 就越短,獨立域名不會共享主域的 Cookie,可以有效減小請求頭大小,這個策略一般稱之為 Cookie-Free Domain)。
節(jié)流
由于我們不能無限制開源,所以節(jié)流也很重要。除了砍掉頁面內(nèi)容,第二次訪問時利用 HTTP 緩存之外,通常能做的就只有合并請求了。根據(jù)合并的內(nèi)容不同,一般又分為以下幾種:
異步接口合并(Batch Ajax Request);
圖片合并,雪碧圖(CSS Sprite);
CSS、JS 合并(Concatenation);
CSS、JS 內(nèi)聯(lián)(Inline);
圖片、音頻內(nèi)聯(lián)(Data URI);
上面這份列表并不完整,我也沒打算列全,這些就足以說明 HTTP/1 時代我們在性能上所做過的不懈努力了??上?,他們并不完美,分別列舉一下他們的缺點:
異步接口合并:批量接口返回的時間受木桶效應(yīng)影響,最慢的那個接口拖累了其他接口。
圖片合并:首先,為了顯示一張小圖,而不得不加載合并后的整張大圖,一是可能浪費流量;二是占用更多內(nèi)存。其次,合并圖片中任何一處修改,都會導(dǎo)致整張大圖緩存失效。這些問題可以根據(jù)不同場景,選用 Data URI、Icon Font、SVG 等技術(shù)來改造。另外,雪碧圖的生成和維護都比較繁瑣,最好使用工具自動管理。
CSS、JS 合并:合并后的資源需要整體加載完才開始解析、執(zhí)行。原本加載完一個文件就可以解析并執(zhí)行一個,將很多個文件合并成一個巨無霸,會整體推后可用時間。為此,Chrome 新版引入了 Script Streaming 技術(shù),能邊加載邊解析 JS 文件。Gmail 為了解決這個問題,將多個 JS 文件合并為一個由多個 inline script 片段組成的 html,用 iframe 引入,以達到邊加載變解析執(zhí)行的效果。另外,與圖片合并類似,CSS、JS 合并也會遇到「無論多小的改動,都會導(dǎo)致整個合并文件緩存失效」的問題。
CSS、JS 內(nèi)聯(lián):上篇文章我詳細分析過內(nèi)聯(lián)的優(yōu)點和弊端。主要兩個問題:1)無法利用緩存;2)多頁面無法共享。
圖片、音頻內(nèi)聯(lián):除了也有上面兩個問題之外,二進制文件以 Data URI 方式內(nèi)聯(lián),需要進行 Base64 編碼,體積會變大 1/3。
結(jié)論
HTTP/1 時代,我們?yōu)榱斯?jié)省昂貴的 HTTP 連接(TCP 連接),采用了各種優(yōu)化手段,這些方案或多或少會引入一些問題,但是相比收益來說還是值得做,也應(yīng)該做。但是,有了 HTTP/2 的多路復(fù)用和頭部壓縮,HTTP 連接變得可以隨心所欲了,本文提到的這些連接數(shù)優(yōu)化手段確實可以退休了。
哦對了,據(jù)官方預(yù)測,HTTP/1 至少還需要 10 年才能徹底退出歷史舞臺,另外盡管 HTTP/2 協(xié)議允許脫離 TSL 部署,但 Chrome 和 Firefox 都表示不支持非 TLS 的 HTTP/2,之后很可能一個網(wǎng)站會同時提供 HTTP/1.1、HTTP/1.1 over TLS、HTTP/2 over TLS 三種服務(wù)。如何在每種情況下,都能給用戶提供最好的體驗,需要更加深入的優(yōu)化研究和更加精細的優(yōu)化策略。由此可見,在很長一段時間內(nèi),WEB 性能優(yōu)化非但不會落幕,反而會更加重要。