在我們所處的互聯(lián)網(wǎng)世界中,HTTP協(xié)議算得上是使用最廣泛的網(wǎng)絡(luò)協(xié)議。最近http2.0的誕生使得它再次互聯(lián)網(wǎng)技術(shù)圈關(guān)注的焦點。任何事物的消退和新生都有其背后推動的力量。對于HTTP來說,這力量復雜來說是各種技術(shù)細節(jié)的演進,簡單來說是用戶體驗和感知的進化。用戶總是希望網(wǎng)絡(luò)上的信息能盡可能快的抵達眼球,越快越好,正是這種對“快”對追逐催生了今天的http2.0。
1. HTTP2.0的前世
http2.0的前世是http1.0和http1.1這兩兄弟。雖然之前僅僅只有兩個版本,但這兩個版本所包含的協(xié)議規(guī)范之龐大,足以讓任何一個有經(jīng)驗的工程師為之頭疼。http1.0誕生于1996年,協(xié)議文檔足足60頁。之后第三年,http1.1也隨之出生,協(xié)議文檔膨脹到了176頁。不過和我們手機端app升級不同的是,網(wǎng)絡(luò)協(xié)議新版本并不會馬上取代舊版本。實際上,1.0和1.1在之后很長的一段時間內(nèi)一直并存,這是由于網(wǎng)絡(luò)基礎(chǔ)設(shè)施更新緩慢所決定的。今天的http2.0也是一樣,新版協(xié)議再好也需要業(yè)界的產(chǎn)品錘煉,需要基礎(chǔ)設(shè)施逐年累月的升級換代才能普及。
1.1 HTTP站在TCP之上
理解http協(xié)議之前一定要對TCP有一定基礎(chǔ)的了解。HTTP是建立在TCP協(xié)議之上,TCP協(xié)議作為傳輸層協(xié)議其實離應(yīng)用層并不遠。HTTP協(xié)議的瓶頸及其優(yōu)化技巧都是基于TCP協(xié)議本身的特性。比如TCP建立連接時三次握手有1.5個RTT(round-trip time)的延遲,為了避免每次請求的都經(jīng)歷握手帶來的延遲,應(yīng)用層會選擇不同策略的http長鏈接方案。又比如TCP在建立連接的初期有慢啟動(slow start)的特性,所以連接的重用總是比新建連接性能要好。
1.1 HTTP應(yīng)用場景
http誕生之初主要是應(yīng)用于web端內(nèi)容獲取,那時候內(nèi)容還不像現(xiàn)在這樣豐富,排版也沒那么精美,用戶交互的場景幾乎沒有。對于這種簡單的獲取網(wǎng)頁內(nèi)容的場景,http表現(xiàn)得還算不錯。但隨著互聯(lián)網(wǎng)的發(fā)展和web2.0的誕生,更多的內(nèi)容開始被展示(更多的圖片文件),排版變得更精美(更多的css),更復雜的交互也被引入(更多的js)。用戶打開一個網(wǎng)站首頁所加載的數(shù)據(jù)總量和請求的個數(shù)也在不斷增加。今天絕大部分的門戶網(wǎng)站首頁大小都會超過2M,請求數(shù)量可以多達100個。另一個廣泛的應(yīng)用是在移動互聯(lián)網(wǎng)的客戶端app,不同性質(zhì)的app對http的使用差異很大。對于電商類app,加載首頁的請求也可能多達10多個。對于微信這類IM,http請求可能僅限于語音和圖片文件的下載,請求出現(xiàn)的頻率并不算高。
1.2 因為延遲,所以慢
影響一個網(wǎng)絡(luò)請求的因素主要有兩個,帶寬和延遲。今天的網(wǎng)絡(luò)基礎(chǔ)建設(shè)已經(jīng)使得帶寬得到極大的提升,大部分時候都是延遲在影響響應(yīng)速度。http1.0被抱怨最多的就是連接無法復用,和head of line blocking這兩個問題。理解這兩個問題有一個十分重要的前提:客戶端是依據(jù)域名來向服務(wù)器建立連接,一般PC端瀏覽器會針對單個域名的server同時建立6~8個連接,手機端的連接數(shù)則一般控制在4~6個。顯然連接數(shù)并不是越多越好,資源開銷和整體延遲都會隨之增大。
連接無法復用會導致每次請求都經(jīng)歷三次握手和慢啟動。三次握手在高延遲的場景下影響較明顯,慢啟動則對文件類大請求影響較大。
head of line blocking會導致帶寬無法被充分利用,以及后續(xù)健康請求被阻塞。假設(shè)有5個請求同時發(fā)出,如下圖:
對于http1.0的實現(xiàn),在第一個請求沒有收到回復之前,后續(xù)從應(yīng)用層發(fā)出的請求只能排隊,請求2,3,4,5只能等請求1的response回來之后才能逐個發(fā)出。網(wǎng)絡(luò)通暢的時候性能影響不大,一旦請求1的request因為什么原因沒有抵達服務(wù)器,或者response因為網(wǎng)絡(luò)阻塞沒有及時返回,影響的就是所有后續(xù)請求,問題就變得比較嚴重了。
1.3 解決連接無法復用
http1.0協(xié)議頭里可以設(shè)置Connection:Keep-Alive。在header里設(shè)置Keep-Alive可以在一定時間內(nèi)復用連接,具體復用時間的長短可以由服務(wù)器控制,一般在15s左右。到http1.1之后Connection的默認值就是Keep-Alive,如果要關(guān)閉連接復用需要顯式的設(shè)置Connection:Close。一段時間內(nèi)的連接復用對PC端瀏覽器的體驗幫助很大,因為大部分的請求在集中在一小段時間以內(nèi)。但對移動app來說,成效不大,app端的請求比較分散且時間跨度相對較大。所以移動端app一般會從應(yīng)用層尋求其它解決方案,長連接方案或者偽長連接方案:
方案一:基于tcp的長鏈接
現(xiàn)在越來越多的移動端app都會建立一條自己的長鏈接通道,通道的實現(xiàn)是基于tcp協(xié)議。基于tcp的socket編程技術(shù)難度相對復雜很多,而且需要自己制定協(xié)議,但帶來的回報也很大。信息的上報和推送變得更及時,在請求量爆發(fā)的時間點還能減輕服務(wù)器壓力(http短連接模式會頻繁的創(chuàng)建和銷毀連接)。不止是IM app有這樣的通道,像淘寶這類電商類app都有自己的專屬長連接通道了?,F(xiàn)在業(yè)界也有不少成熟的方案可供選擇了,google的protobuf就是其中之一。
方案二:http long-polling
long-polling可以用下圖表示:
客戶端在初始狀態(tài)就會發(fā)送一個polling請求到服務(wù)器,服務(wù)器并不會馬上返回業(yè)務(wù)數(shù)據(jù),而是等待有新的業(yè)務(wù)數(shù)據(jù)產(chǎn)生的時候再返回。所以連接會一直被保持,一旦結(jié)束馬上又會發(fā)起一個新的polling請求,如此反復,所以一直會有一個連接被保持。服務(wù)器有新的內(nèi)容產(chǎn)生的時候,并不需要等待客戶端建立一個新的連接。做法雖然簡單,但有些難題需要攻克才能實現(xiàn)穩(wěn)定可靠的業(yè)務(wù)框架:
- 和傳統(tǒng)的http短鏈接相比,長連接會在用戶增長的時候極大的增加服務(wù)器壓力
- 移動端網(wǎng)絡(luò)環(huán)境復雜,像wifi和4g的網(wǎng)絡(luò)切換,進電梯導致網(wǎng)絡(luò)臨時斷掉等,這些場景都需要考慮怎么重建健康的連接通道。
- 這種polling的方式穩(wěn)定性并不好,需要做好數(shù)據(jù)可靠性的保證,比如重發(fā)和ack機制。
- polling的response有可能會被中間代理cache住,要處理好業(yè)務(wù)數(shù)據(jù)的過期機制。
long-polling方式還有一些缺點是無法克服的,比如每次新的請求都會帶上重復的header信息,還有數(shù)據(jù)通道是單向的,主動權(quán)掌握在server這邊,客戶端有新的業(yè)務(wù)請求的時候無法及時傳送。
方案三:http streaming
http streaming流程大致如下:
同long-polling不同的是,server并不會結(jié)束初始的streaming請求,而是持續(xù)的通過這個通道返回最新的業(yè)務(wù)數(shù)據(jù)。顯然這個數(shù)據(jù)通道也是單向的。streaming是通過在server response的頭部里增加”Transfer Encoding: chunked”來告訴客戶端后續(xù)還會有新的數(shù)據(jù)到來。除了和long-polling相同的難點之外,streaming還有幾個缺陷:
有些代理服務(wù)器會等待服務(wù)器的response結(jié)束之后才會將結(jié)果推送到請求客戶端。對于streaming這種永遠不會結(jié)束的方式來說,客戶端就會一直處于等待response的過程中。
業(yè)務(wù)數(shù)據(jù)無法按照請求來做分割,所以客戶端沒收到一塊數(shù)據(jù)都需要自己做協(xié)議解析,也就是說要做自己的協(xié)議定制。
streaming不會產(chǎn)生重復的header數(shù)據(jù)。
方案四:web socket
WebSocket和傳統(tǒng)的tcp socket連接相似,也是基于tcp協(xié)議,提供雙向的數(shù)據(jù)通道。WebSocket優(yōu)勢在于提供了message的概念,比基于字節(jié)流的tcp socket使用更簡單,同時又提供了傳統(tǒng)的http所缺少的長連接功能。不過WebSocket相對較新,2010年才起草,并不是所有的瀏覽器都提供了支持。各大瀏覽器廠商最新的版本都提供了支持。
1.4 解決head of line blocking
Head of line blocking(以下簡稱為holb)是http2.0之前網(wǎng)絡(luò)體驗的最大禍源。正如圖1中所示,健康的請求會被不健康的請求影響,而且這種體驗的損耗受網(wǎng)絡(luò)環(huán)境影響,出現(xiàn)隨機且難以監(jiān)控。為了解決holb帶來的延遲,協(xié)議設(shè)計者設(shè)計了一種新的pipelining機制。
http pipelining
pipelining的流程圖可以用下圖表示:
和圖一相比最大的差別是,請求2,3,4,5不用等請求1的response返回之后才發(fā)出,而是幾乎在同一時間把request發(fā)向了服務(wù)器。2,3,4,5及所有后續(xù)共用該連接的請求節(jié)約了等待的時間,極大的降低了整體延遲。下圖可以清晰的看出這種新機制對延遲的改變:
不過pipelining并不是救世主,它也存在不少缺陷:
- pipelining只能適用于http1.1,一般來說,支持http1.1的server都要求支持pipelining。
- 只有冪等的請求(GET,HEAD)能使用pipelining,非冪等請求比如POST不能使用,因為請求之間可能會存在先后依賴關(guān)系。
- head of line blocking并沒有完全得到解決,server的response還是要求依次返回,遵循FIFO(first in first out)原則。也就是說如果請求1的response沒有回來,2,3,4,5的response也不會被送回來。
- 絕大部分的http代理服務(wù)器不支持pipelining。
- 和不支持pipelining的老服務(wù)器協(xié)商有問題。
- 可能會導致新的Front of queue blocking問題。
正是因為有這么多的問題,各大瀏覽器廠商要么是根本就不支持pipelining,要么就是默認關(guān)掉了pipelining機制,而且啟用的條件十分苛刻??梢詤⒖糲hrome對于pipeling的問題描述。
1.5 其它奇技淫巧
為了解決延遲帶來的苦惱,永遠都會有聰明的探索者找出新的捷徑來?;ヂ?lián)網(wǎng)的蓬勃興盛催生出了各種新奇技巧,我們來依次看下這些“捷徑”及各自的優(yōu)缺點。
Spriting(圖片合并)
Spriting指的是將多個小圖片合并到一張大的圖片里,這樣多個小的請求就被合并成了一個大的圖片請求,然后再利用js或者css文件來取出其中的小張圖片使用。好處顯而易見,請求數(shù)減少,延遲自然低。壞處是文件的粒度變大了,有時候我們可能只需要其中一張小圖,卻不得不下載整張大圖,cache處理也變得麻煩,在只有一張小圖過期的情況下,為了獲得最新的版本,不得不從服務(wù)器下載完整的大圖,即使其它的小圖都沒有過期,顯然浪費了流量。
Inlining(內(nèi)容內(nèi)嵌)
Inlining的思考角度和spriting類似,是將額外的數(shù)據(jù)請求通過base64編碼之后內(nèi)嵌到一個總的文件當中。比如一個網(wǎng)頁有一張背景圖,我們可以通過如下代碼嵌入:
background: url(data:image/png;base64,)
data部分是base64編碼之后的字節(jié)碼,這樣也避免了一次多余的http請求。但這種做法也有著和spriting相同的問題,資源文件被綁定到了其它文件,粒度變得難以控制。
Concatenation(文件合并)
Concatenation主要是針對js這類文件,現(xiàn)在前端開發(fā)交互越來越多,零散的js文件也在變多。將多個js文件合并到一個大的文件里在做一些壓縮處理也可以減小延遲和傳輸?shù)臄?shù)據(jù)量。但同樣也面臨著粒度變大的問題,一個小的js代碼改動會導致整個js文件被下載。
Domain Sharding(域名分片)
前面我提到過很重要的一點,瀏覽器或者客戶端是根據(jù)domain(域名)來建立連接的。比如針對www.example.com只允許同時建立2個連接,但mobile.example.com被認為是另一個域名,可以再建立兩個新的連接。依次類推,如果我再多建立幾個sub domain(子域名),那么同時可以建立的http請求就會更多,這就是Domain Sharding了。連接數(shù)變多之后,受限制的請求就不需要等待前面的請求完成才能發(fā)出了。這個技巧被大量的使用,一個頗具規(guī)模的網(wǎng)頁請求數(shù)可以超過100,使用domain sharding之后同時建立的連接數(shù)可以多到50個甚至更多。
這么做當然增加了系統(tǒng)資源的消耗,但現(xiàn)在硬件資源升級非常之快,和用戶寶貴的等待時機相比起來實在微不足道。
domain sharding還有一大好處,對于資源文件來說一般是不需要cookie的,將這些不同的靜態(tài)資源文件分散在不同的域名服務(wù)器上,可以減小請求的size。
不過domain sharding只有在請求數(shù)非常之多的場景下才有明顯的效果。而且請求數(shù)也不是越多越好,資源消耗是一方面,另一點是由于tcp的slow start會導致每個請求在初期都會經(jīng)歷slow start,還有tcp 三次握手,DNS查詢的延遲。這一部分帶來的時間損耗和請求排隊同樣重要,到底怎么去平衡這二者就需要取一個可靠的連接數(shù)中間值,這個值的最終確定要通過反復的測試。移動端瀏覽器場景建議不要使用domain sharding,具體細節(jié)參考這篇文章。
2. 開拓者SPDY
http1.0和1.1雖然存在這么多問題,業(yè)界也想出了各種優(yōu)化的手段,但這些方法手段都是在嘗試繞開協(xié)議本身的缺陷,都有種隔靴搔癢,治標不治本的感覺。直到2012年google如一聲驚雷提出了SPDY的方案,大家才開始從正面看待和解決老版本http協(xié)議本身的問題,這也直接加速了http2.0的誕生。實際上,http2.0是以SPDY為原型進行討論和標準化的。為了給http2.0讓路,google已決定在2016年不再繼續(xù)支持SPDY開發(fā),但在http2.0出生之前,SPDY已經(jīng)有了相當規(guī)模的應(yīng)用,作為一個過渡方案恐怕在還將一段時間內(nèi)繼續(xù)存在。現(xiàn)在不少app客戶端和server都已經(jīng)使用了SPDY來提升體驗,http2.0在老的設(shè)備和系統(tǒng)上還無法使用(iOS系統(tǒng)只有在iOS9+上才支持),所以可以預(yù)見未來幾年spdy將和http2.0共同服務(wù)的情況。
2.1 SPDY的目標
SPDY的目標在一開始就是瞄準http1.x的痛點,即延遲和安全性。我們上面通篇都在討論延遲,至于安全性,由于http是明文協(xié)議,其安全性也一直被業(yè)界詬病,不過這是另一個大的話題。如果以降低延遲為目標,應(yīng)用層的http和傳輸層的tcp都是都有調(diào)整的空間,不過tcp作為更底層協(xié)議存在已達數(shù)十年之久,其實現(xiàn)已深植全球的網(wǎng)絡(luò)基礎(chǔ)設(shè)施當中,如果要動必然傷經(jīng)動骨,業(yè)界響應(yīng)度必然不高,所以SPDY的手術(shù)刀對準的是http。
降低延遲,客戶端的單連接單請求,server的FIFO響應(yīng)隊列都是延遲的大頭。
http最初設(shè)計都是客戶端發(fā)起請求,然后server響應(yīng),server無法主動push內(nèi)容到客戶端。
壓縮HTTP Header,http1.x的header越來越膨脹,cookie和user agent很容易讓header的size增至1kb大小,甚至更多。而且由于http的無狀態(tài)特性,header必須每次request都重復攜帶,很浪費流量。
為了增加業(yè)界響應(yīng)的可能性,聰明的google一開始就避開了從傳輸層動手,而且打算利用開源社區(qū)的力量以提高擴散的力度,對于協(xié)議使用者來說,也只需要在請求的header里設(shè)置user agent,然后在server端做好支持即可,極大的降低了部署的難度。SPDY的設(shè)計如下:
SPDY位于HTTP之下,TCP和SSL之上,這樣可以輕松兼容老版本的HTTP協(xié)議(將http1.x的內(nèi)容封裝成一種新的frame格式),同時可以使用已有的SSL功能。SPDY的功能可以分為基礎(chǔ)功能和高級功能兩部分,基礎(chǔ)功能默認啟用,高級功能需要手動啟用。
SPDY基礎(chǔ)功能
多路復用(multiplexing)。多路復用通過多個請求stream共享一個tcp連接的方式,解決了http1.x holb(head of line blocking)的問題,降低了延遲同時提高了帶寬的利用率。
請求優(yōu)先級(request prioritization)。多路復用帶來一個新的問題是,在連接共享的基礎(chǔ)之上有可能會導致關(guān)鍵請求被阻塞。SPDY允許給每個request設(shè)置優(yōu)先級,這樣重要的請求就會優(yōu)先得到響應(yīng)。比如瀏覽器加載首頁,首頁的html內(nèi)容應(yīng)該優(yōu)先展示,之后才是各種靜態(tài)資源文件,腳本文件等加載,這樣可以保證用戶能第一時間看到網(wǎng)頁內(nèi)容。
header壓縮。前面提到過幾次http1.x的header很多時候都是重復多余的。選擇合適的壓縮算法可以減小包的大小和數(shù)量。SPDY對header的壓縮率可以達到80%以上,低帶寬環(huán)境下效果很大。
SPDY高級功能
server推送(server push)。http1.x只能由客戶端發(fā)起請求,然后服務(wù)器被動的發(fā)送response。開啟server push之后,server通過X-Associated-Content header(X-開頭的header都屬于非標準的,自定義header)告知客戶端會有新的內(nèi)容推送過來。在用戶第一次打開網(wǎng)站首頁的時候,server將資源主動推送過來可以極大的提升用戶體驗。
server暗示(server hint)。和server push不同的是,server hint并不會主動推送內(nèi)容,只是告訴有新的內(nèi)容產(chǎn)生,內(nèi)容的下載還是需要客戶端主動發(fā)起請求。server hint通過X-Subresources header來通知,一般應(yīng)用場景是客戶端需要先查詢server狀態(tài),然后再下載資源,可以節(jié)約一次查詢請求。
2.2 SPDY的成績
SPDY的成績可以用google官方的一個數(shù)字來說明:頁面加載時間相比于http1.x減少了64%。而且各大瀏覽器廠商在SPDY誕生之后的1年多里都陸續(xù)支持了SPDY,不少大廠app和server端框架也都將SPDY應(yīng)用到了線上的產(chǎn)品當中。
google的官網(wǎng)也給出了他們自己做的一份測試數(shù)據(jù)。測試對象是25個訪問量排名靠前的網(wǎng)站首頁,家用網(wǎng)絡(luò)%1的丟包率,每個網(wǎng)站測試10次取平均值。結(jié)果如下:
不開啟ssl的時候提升在 27% – 60%,開啟之后為39% – 55%。 這份測試結(jié)果有兩點值得特別注意:
連接數(shù)的選擇
連接到底是基于域名來建立,還是不做區(qū)分所有子域名都共享一個連接,這個策略選擇上值得商榷。google的測試結(jié)果測試了兩種方案,看結(jié)果似乎是單一連接性能高于多域名連接方式。之所以出現(xiàn)這種情況是由于網(wǎng)頁所有的資源請求并不是同一時間發(fā)出,后續(xù)發(fā)出的子域名請求如果能復用之前的tcp連接當然性能更好。實際應(yīng)用場景下應(yīng)該也是單連接共享模式表現(xiàn)好。
帶寬的影響
測試基于兩種帶寬環(huán)境,一慢一快。網(wǎng)速快的環(huán)境下對減小延遲的提升更大,單連接模式下可以提升至60%。原因也比較簡單,帶寬越大,復用連接的請求完成越快,由于三次握手和慢啟動導致的延遲損耗就變得更明顯。
出了連接模式和帶寬之外,丟包率和RTT也是需要測試的參數(shù)。SPDY對header的壓縮有80%以上,整體包大小能減少大概40%,發(fā)送的包越少,自然受丟包率影響也就越小,所以丟包率大的惡劣環(huán)境下SPDY反而更能提升體驗。下圖是受丟包率影響的測試結(jié)果,丟包率超過2.5%之后就沒有提升了:
RTT越大,延遲會越大,在高RTT的場景下,由于SPDY的request是并發(fā)進行的,所有對包的利用率更高,反而能更明顯的減小總體延遲。測試結(jié)果如下:
SPDY從2012年誕生到2016停止維護,時間跨度對于網(wǎng)絡(luò)協(xié)議來說其實非常之短。如果HTTP2.0沒有出來,google或許能收集到更多業(yè)界產(chǎn)品的真實反饋和數(shù)據(jù),畢竟google自己的測試環(huán)境相對簡單。但SPDY也完成了自己的使命,作為一貫扮演拓荒者角色的google應(yīng)該也早就預(yù)見了這樣的結(jié)局。SPDY對產(chǎn)品網(wǎng)絡(luò)體驗的提升到底如何,恐怕只有各大廠產(chǎn)品經(jīng)理才清楚了。
3. 救世主HTTP2.0
SPDY的誕生和表現(xiàn)說明了兩件事情:一是在現(xiàn)有互聯(lián)網(wǎng)設(shè)施基礎(chǔ)和http協(xié)議廣泛使用的前提下,是可以通過修改協(xié)議層來優(yōu)化http1.x的。二是針對http1.x的修改確實效果明顯而且業(yè)界反饋很好。正是這兩點讓IETF(Internet Enginerring Task Force)開始正式考慮制定HTTP2.0的計劃,最后決定以SPDY/3為藍圖起草HTTP2.0,SPDY的部分設(shè)計人員也被邀請參與了HTTP2.0的設(shè)計。
3.1 HTTP2.0需要考慮的問題
HTTP2.0與SPDY的起點不同,SPDY可以說是google的“玩具”,最早出現(xiàn)在自家的chrome瀏覽器和server上,好不好玩以及別人會不會跟著一起玩對google來說無關(guān)痛癢。但HTTP2.0作為業(yè)界標準還沒出生就是眾人矚目的焦點,一開始如果有什么瑕疵或者不兼容的問題影響可能又是數(shù)十年之久,所以考慮的問題和角度要非常之廣。我們來看下HTTP2.0一些重要的設(shè)計前提:
- 客戶端向server發(fā)送request這種基本模型不會變。
- 老的scheme不會變,使用http://和https://的服務(wù)和應(yīng)用不會要做任何更改,不會有http2://。
- 使用http1.x的客戶端和服務(wù)器可以無縫的通過代理方式轉(zhuǎn)接到http2.0上。
- 不識別http2.0的代理服務(wù)器可以將請求降級到http1.x。
因為客戶端和server之間在確立使用http1.x還是http2.0之前,必須要要確認對方是否支持http2.0,所以這里必須要有個協(xié)商的過程。最簡單的協(xié)商也要有一問一答,客戶端問server答,即使這種最簡單的方式也多了一個RTT的延遲,我們之所以要修改http1.x就是為了降低延遲,顯然這個RTT我們是無法接受的。google制定SPDY的時候也遇到了這個問題,他們的辦法是強制SPDY走https,在SSL層完成這個協(xié)商過程。ssl層的協(xié)商在http協(xié)議通信之前,所以是最適合的載體。google為此做了一個tls的拓展,叫NPN(Next Protocol Negotiation),從名字上也可以看出,這個拓展主要目的就是為了協(xié)商下一個要使用的協(xié)議。HTTP2.0雖然也采用了相同的方式,不過HTTP2.0經(jīng)過激烈的討論,最終還是沒有強制HTTP2.0要走ssl層,大部分瀏覽器廠商(除了IE)卻只實現(xiàn)了基于https的2.0協(xié)議。HTTP2.0沒有使用NPN,而是另一個tls的拓展叫ALPN(Application Layer Protocol Negotiation)。SPDY也打算從NPN遷移到ALPN了。
各瀏覽器(除了IE)之所以只實現(xiàn)了基于SSL的HTTP2.0,另一個原因是走SSL請求的成功率會更高,被SSL封裝的request不會被監(jiān)聽和修改,這樣網(wǎng)絡(luò)中間的網(wǎng)絡(luò)設(shè)備就無法基于http1.x的認知去干涉修改request,http2.0的request如果被意外的修改,請求的成功率自然會下降。
HTTP2.0協(xié)議沒有強制使用SSL是因為聽到了很多的反對聲音,畢竟https和http相比,在不優(yōu)化的前提下性能差了不少,要把https優(yōu)化到幾乎不增加延遲的程度又需要花費不少力氣。IETF面對這種兩難的處境做了妥協(xié),但大部分瀏覽器廠商(除了IE)并不買帳,他們只認https2.0。對于app開發(fā)者來說,他們可以堅持使用沒有ssl的http2.0,不過要承擔一個多余的RTT延遲和請求可能被破壞的代價。
3.1 HTTP2.0主要改動
HTTP2.0作為新版協(xié)議,改動細節(jié)必然很多,不過對應(yīng)用開發(fā)者和服務(wù)提供商來說,影響較大的就幾點。
新的二進制格式(Binary Format)
http1.x誕生的時候是明文協(xié)議,其格式由三部分組成:start line(request line或者status line),header,body。要識別這3部分就要做協(xié)議解析,http1.x的解析是基于文本。基于文本協(xié)議的格式解析存在天然缺陷,文本的表現(xiàn)形式有多樣性,要做到健壯性考慮的場景必然很多,二進制則不同,只認0和1的組合。基于這種考慮http2.0的協(xié)議解析決定采用二進制格式,實現(xiàn)方便且健壯。
有人可能會覺得基于文本的http調(diào)試方便很多,像firebug,chrome,charles等不少工具都可以即時調(diào)試修改請求。實際上現(xiàn)在很多請求都是走https了,要調(diào)試https請求必須有私鑰才行。http2.0的絕大部分request應(yīng)該都是走https,所以調(diào)試方便無法作為一個有力的考慮因素了。curl,tcpdump,wireshark這些工具會更適合http2.0的調(diào)試。
http2.0用binary格式定義了一個一個的frame,和http1.x的格式對比如下圖:
http2.0的格式定義更接近tcp層的方式,這張二機制的方式十分高效且精簡。length定義了整個frame的開始到結(jié)束,type定義frame的類型(一共10種),flags用bit位定義一些重要的參數(shù),stream id用作流控制,剩下的payload就是request的正文了。
雖然看上去協(xié)議的格式和http1.x完全不同了,實際上http2.0并沒有改變http1.x的語義,只是把原來http1.x的header和body部分用frame重新封裝了一層而已。調(diào)試的時候瀏覽器甚至會把http2.0的frame自動還原成http1.x的格式。具體的協(xié)議關(guān)系可以用下圖表示:
連接共享
http2.0要解決的一大難題就是多路復用(MultiPlexing),即連接共享。上面協(xié)議解析中提到的stream id就是用作連接共享機制的。一個request對應(yīng)一個stream并分配一個id,這樣一個連接上可以有多個stream,每個stream的frame可以隨機的混雜在一起,接收方可以根據(jù)stream id將frame再歸屬到各自不同的request里面。
前面還提到過連接共享之后,需要優(yōu)先級和請求依賴的機制配合才能解決關(guān)鍵請求被阻塞的問題。http2.0里的每個stream都可以設(shè)置又優(yōu)先級(Priority)和依賴(Dependency)。優(yōu)先級高的stream會被server優(yōu)先處理和返回給客戶端,stream還可以依賴其它的sub streams。優(yōu)先級和依賴都是可以動態(tài)調(diào)整的。動態(tài)調(diào)整在有些場景下很有用,假想用戶在用你的app瀏覽商品的時候,快速的滑動到了商品列表的底部,但前面的請求先發(fā)出,如果不把后面的請求優(yōu)先級設(shè)高,用戶當前瀏覽的圖片要到最后才能下載完成,顯然體驗沒有設(shè)置優(yōu)先級好。同理依賴在有些場景下也有妙用。
header壓縮
前面提到過http1.x的header由于cookie和user agent很容易膨脹,而且每次都要重復發(fā)送。http2.0使用encoder來減少需要傳輸?shù)膆eader大小,通訊雙方各自cache一份header fields表,既避免了重復header的傳輸,又減小了需要傳輸?shù)拇笮?。高效的壓縮算法可以很大的壓縮header,減少發(fā)送包的數(shù)量從而降低延遲。
這里普及一個小知識點?,F(xiàn)在大家都知道tcp有slow start的特性,三次握手之后開始發(fā)送tcp segment,第一次能發(fā)送的沒有被ack的segment數(shù)量是由initial tcp window大小決定的。這個initial tcp window根據(jù)平臺的實現(xiàn)會有差異,但一般是2個segment或者是4k的大小(一個segment大概是1500個字節(jié)),也就是說當你發(fā)送的包大小超過這個值的時候,要等前面的包被ack之后才能發(fā)送后續(xù)的包,顯然這種情況下延遲更高。intial window也并不是越大越好,太大會導致網(wǎng)絡(luò)節(jié)點的阻塞,丟包率就會增加,具體細節(jié)可以參考IETF這篇文章。http的header現(xiàn)在膨脹到有可能會超過這個intial window的值了,所以更顯得壓縮header的重要性。
壓縮算法的選擇
SPDY/2使用的是gZIP壓縮算法,但后來出現(xiàn)的兩種攻擊方式BREACH和CRIME使得即使走ssl的SPDY也可以被破解內(nèi)容,最后綜合考慮采用的是一種叫HPACK的壓縮算法。這兩個漏洞和相關(guān)算法可以點擊鏈接查看更多的細節(jié),不過這種漏洞主要存在于瀏覽器端,因為需要通過javascript來注入內(nèi)容并觀察payload的變化。
重置連接表現(xiàn)更好
很多app客戶端都有取消圖片下載的功能場景,對于http1.x來說,是通過設(shè)置tcp segment里的reset flag來通知對端關(guān)閉連接的。這種方式會直接斷開連接,下次再發(fā)請求就必須重新建立連接。http2.0引入RST_STREAM類型的frame,可以在不斷開連接的前提下取消某個request的stream,表現(xiàn)更好。
Server Push
Server Push的功能前面已經(jīng)提到過,http2.0能通過push的方式將客戶端需要的內(nèi)容預(yù)先推送過去,所以也叫“cache push”。另外有一點值得注意的是,客戶端如果退出某個業(yè)務(wù)場景,出于流量或者其它因素需要取消server push,也可以通過發(fā)送RST_STREAM類型的frame來做到。
流量控制(Flow Control)
TCP協(xié)議通過sliding window的算法來做流量控制。發(fā)送方有個sending window,接收方有receive window。http2.0的flow control是類似receive window的做法,數(shù)據(jù)的接收方通過告知對方自己的flow window大小表明自己還能接收多少數(shù)據(jù)。只有Data類型的frame才有flow control的功能。對于flow control,如果接收方在flow window為零的情況下依然更多的frame,則會返回block類型的frame,這張場景一般表明http2.0的部署出了問題。
Nagle Algorithm vs TCP Delayed Ack
tcp協(xié)議優(yōu)化的一個經(jīng)典場景是:Nagle算法和Berkeley的delayed ack算法的對立。http2.0并沒有對tcp層做任何修改,所以這種對立導致的高延遲問題依然存在。要么通過TCP_NODELAY禁用Nagle算法,要么通過TCP_QUICKACK禁用delayed ack算法。貌似http2.0官方建議是設(shè)置TCP_NODELAY。
更安全的SSL
HTTP2.0使用了tls的拓展ALPN來做協(xié)議升級,除此之外加密這塊還有一個改動,HTTP2.0對tls的安全性做了近一步加強,通過黑名單機制禁用了幾百種不再安全的加密算法,一些加密算法可能還在被繼續(xù)使用。如果在ssl協(xié)商過程當中,客戶端和server的cipher suite沒有交集,直接就會導致協(xié)商失敗,從而請求失敗。在server端部署http2.0的時候要特別注意這一點。
3.2 HTTP2.0里的負能量
SPDY和HTTP2.0之間的曖昧關(guān)系,以及google作為SPDY的創(chuàng)造者,這兩點很容易讓陰謀論者懷疑google是否會成為協(xié)議的最終收益方。這其實是廢話,google當然會受益,任何新協(xié)議使用者都會從中受益,至于誰吃肉,誰喝湯看的是自己的本事。從整個協(xié)議的變遷史也可以粗略看出,新協(xié)議的誕生完全是針對業(yè)界現(xiàn)存問題對癥下藥,并沒有g(shù)oogle業(yè)務(wù)相關(guān)的痕跡存在,google至始至終只扮演了一個角色:you can you up。
HTTP2.0不會是萬金油,但抹了也不會有副作用。HTTP2.0最大的亮點在于多路復用,而多路復用的好處只有在http請求量大的場景下才明顯,所以有人會覺得只適用于瀏覽器瀏覽大型站點的時候。這么說其實沒錯,但http2.0的好處不僅僅是multiplexing,請求壓縮,優(yōu)先級控制,server push等等都是亮點。對于內(nèi)容型移動端app來說,比如淘寶app,http請求量大,多路復用還是能產(chǎn)生明顯的體驗提升。多路復用對延遲的改變可以參考下這個測試網(wǎng)址。
HTTP2.0對于ssl的依賴使得有些開發(fā)者望而生畏。不少開發(fā)者對ssl還停留在高延遲,CPU性能損耗,配置麻煩的印象中。其實ssl于http結(jié)合對性能的影響已經(jīng)可以優(yōu)化到忽略的程度了,網(wǎng)上也有不少文章可以參考。HTTP2.0也可以不走ssl,有些場景確實可能不適合https,比如對代理服務(wù)器的cache依賴,對于內(nèi)容安全性不敏感的get請求可以通過代理服務(wù)器緩存來優(yōu)化體驗。
3.3 HTTP2.0的現(xiàn)狀
HTTP2.0作為新版本的網(wǎng)絡(luò)協(xié)議肯定需要一段時間去普及,但HTTP本身屬于應(yīng)用層協(xié)議,和當年的網(wǎng)絡(luò)層協(xié)議IPV6不同,離底層協(xié)議越遠,對網(wǎng)絡(luò)基礎(chǔ)硬件設(shè)施的影響就越小。HTTP2.0甚至還特意的考慮了與HTTP1.x的兼容問題,只是在HTTP1.x的下面做了一層framing layer,更使得其普及的阻力變小。所以不出意外,HTTP2.0的普及速度可能會遠超大部分人的預(yù)期。
Firefox 2015年在其瀏覽器流量中檢測到,有13%的http流量已經(jīng)使用了http2.0,27%的https也是http2.0,而且還處于持續(xù)的增長當中。一般用戶察覺不到是否使用了http2.0,不過可以裝這樣一個插件,安裝之后如果網(wǎng)站是http2.0的,在地址欄的最右邊會有個閃電圖標。還可以使用這個網(wǎng)站來測試。對于開發(fā)者來說,可以通過Web Developer的Network來查看協(xié)議細節(jié),如下圖:
其中Version:HTTP/2.0已經(jīng)很明確表明協(xié)議類型,F(xiàn)irefox還在header里面插入了X-Firefox-Spdy:“h2”,也可以看出是否使用http2.0。
Chrome在2015年檢測到的http2.0流量大概有18%。不過這個數(shù)字本來會更高,因為Chrome現(xiàn)在很大一部分流量都在試驗QUIC(google正在開辟的另一塊疆土)。Chrome上也可以使用類似的插件來判斷網(wǎng)站是否是使用http2.0。
4. 移動端HTTP現(xiàn)狀
4.1 iOS下http現(xiàn)狀
iOS系統(tǒng)是從iOS8開始才通過NSURLSession來支持SPDY的,iOS9+開始自動支持http2.0。實際上apple對http2.0非常有信心,推廣力度也很大。新版本ATS機制默認使用https來進行網(wǎng)絡(luò)傳輸。APN(Apple Push Notifiction)在iOS9上也已經(jīng)是通過http2.0來實現(xiàn)的了。iOS9 sdk里的NSURLSession默認使用http2.0,而且對開發(fā)者來說是完全透明的,甚至沒有api來知道到底是用的哪個版本的http協(xié)議。
對于開發(fā)者來說到底怎么去配置最佳的http使用方案呢?在我看來,因app而異,主要從兩方面來考慮:一是app本身http流量是否大而且密集,二是開發(fā)團隊本身的技術(shù)條件。http2.0的部署相對容易很多,客戶端開發(fā)者甚至不用做什么改動,只需要使用iOS9的SDK編譯即可,但缺點是http2.0只能適用于iOS9的設(shè)備。SPDY的部署相對麻煩一些,但優(yōu)點是可以兼顧iOS6+的設(shè)備。iOS端的SPDY可以使用twitter開發(fā)的CocoaSPDY方案,但有一點需要特別處理:
由于蘋果的TLS實現(xiàn)不支持NPN,所以通過NPN協(xié)商使用SPDY就無法通過默認443端口來實現(xiàn)。有兩種做法,一是客戶端和server同時約定好使用另一個端口號來做NPN協(xié)商,二是server這邊通過request header智能判斷客戶端是否支持SPDY而越過NPN協(xié)商過程。第一種方法會簡單一點,不過需要從框架層將所有的http請求都map到另一個port,url mapping可以參考我之前的一篇文章。twitter自己的網(wǎng)站twitter.com使用的是第二種方法。
瀏覽器端(比如Chrome),server端(比如nginx)都陸續(xù)打算放棄支持spdy了,畢竟google官方都宣布要停止維護了。spdy會是一個過渡方案,會隨著iOS9的普及會逐步消失,所以這部分的技術(shù)投入需要開發(fā)團隊自己去衡量。
4.2 Android下http現(xiàn)狀
android和iOS情況類似,http2.0只能在新系統(tǒng)下支持,spdy作為過渡方案仍然有存在的必要。
對于使用webview的app來說,需要基于chrome內(nèi)核的webview才能支持spdy和http2.0,而android系統(tǒng)的webview是從android4.4(KitKat)才改成基于chrome內(nèi)核的。
對于使用native api調(diào)用的http請求來說,OKHttp是同時支持spdy和http2.0的可行方案。如果使用ALPN,okhttp要求android系統(tǒng)5.0+(實際上,android4.4上就有了ALPN的實現(xiàn),不過有bug,知道5.0才正式修復),如果使用NPN,可以從android4.0+開始支持,不過NPN也是屬于將要被淘汰的協(xié)議。
結(jié)束語
以上是HTTP從1.x到SPDY,再到HTTP2.0的一些主要變遷技術(shù)點。HTTP2.0正處于逐步應(yīng)用到線上產(chǎn)品和服務(wù)的階段,可以預(yù)見未來會有不少新的坑產(chǎn)生和與之對應(yīng)的優(yōu)化技巧,HTTP1.x和SPDY也將在一段時間內(nèi)繼續(xù)發(fā)揮余熱。作為工程師,需要了解這些協(xié)議背后的技術(shù)細節(jié),才能打造高性能的網(wǎng)絡(luò)框架,從而提升我們的產(chǎn)品體驗。
您可能感興趣的文章:- HTTP 錯誤 500.19- Internal Server Error 錯誤解決方法
- HTTP 錯誤 403.1 - 禁止訪問:執(zhí)行訪問被拒絕。 解決方法該頁無法顯示
- http協(xié)議詳解(超詳細)
- HttpWebRequest和HttpWebResponse用法小結(jié)
- apache https配置詳細步驟講解
- java使用httpclient模擬post請求和get請求示例
- C#中HttpWebRequest的用法詳解
- javascript XMLHttpRequest對象全面剖析
- HTTP提交方式之PUT詳細介紹及POST和PUT的區(qū)別
- PHP獲取http請求的頭信息實現(xiàn)步驟
- IIS HTTP 錯誤 404.17 - Not Found 解決方法