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