再談HTTP2性能提升之背後原理- HTTP2歷史解剖
即使千辛萬苦,還是把網站升級到http2了,遇坑如《phpcms v9站http升級到https加http2遇到到坑》。因為理論相比於HTTP 1.x ,在同時兼容 HTTP/1.1 完全語義,進一步減少了網絡延遲。對於前端開發人員來說,無疑減少了在前端方面的優化工作。
http1.0的缺點
http1.0被抱怨最多的就是連接無法復用,和head of line blocking這兩個問題。理解這兩個問題有一個十分重要的前提:客戶端是依據來向服務器建立連接,一般PC端瀏覽器會針對單個域名的server同時建立6~8個連接,手機端的連接數則一般控制在4 ~6個。顯然連接數並不是越多越好,資源開銷和整體延遲都會隨之增大。
連接無法復用會導致每次請求都經曆三次握手和慢啟動。三次握手在高延遲的場景下影響較明顯,慢啟動則對文件類大請求影響較大。
head of line blocking會導致帶寬無法被充分利用,以及後續健康請求被阻塞。假設有5個請求同時發出,對於http1.0的實現,在第一個請求沒有收到回復之前,後續從應用層發出的請求只能排隊,請求2,3,4,5只能等請求1的response回來之後才能逐個發出。一旦請求1的request因為什麼原因沒有抵達服務器,或者response因為網絡阻塞沒有及時返回,影響的就是所有後續請求,問題就變得比較嚴重了。
對於http1.0的缺點優化方案
解決連接無法復用
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-polling
客戶端在初始狀態就會發送一個polling請求到服務器,服務器並不會馬上返回業務數據,而是等待有新的業務數據產生的時候再返回。所以連接會一直被保持,一旦結束馬上又會發起一個新的polling請求,如此反复,所以一直會有一個連接被保持。服務器有新的內容產生的時候,並不需要等待客戶端建立一個新的連接。做法雖然簡單,但有些難題需要攻克才能實現穩定可靠的業務框架:
- 和傳統的http短鏈接相比,長連接會在用戶增長的時候極大的增加服務器壓力
- 移動端網絡環境複雜,像wifi和4g的網絡切換,進電梯導致網絡臨時斷掉等,這些場景都需要考慮怎麼重建健康的連接通道。
- 這種polling的方式穩定性並不好,需要做好數據可靠性的保證,比如重發和ack機制。
- polling的response有可能會被中間代理cache住,要處理好業務數據的過期機制。
long-polling方式還有一些缺點是無法克服的,比如每次新的請求都會帶上重複的header信息,還有數據通道是單向的,主動權掌握在server這邊,客戶端有新的業務請求的時候無法及時傳送。
方案三:http streaming
同long-polling不同的是,server並不會結束初始的streaming請求,而是持續的通過這個通道返回最新的業務數據。顯然這個數據通道也是單向的。streaming是通過在server response的頭部裡增加”Transfer Encoding: chunked”來告訴客戶端后續還會有新的數據到來。除了和long-polling相同的難點之外,streaming還有幾個缺陷:
- 有些代理服務器會等待服務器的response結束之後才會將結果推送到請求客戶端。對於streaming這種永遠不會結束的方式來說,客戶端就會一直處於等待response的過程中。
- 業務數據無法按照請求來做分割,所以客戶端沒收到一塊數據都需要自己做協議解析,也就是說要做自己的協議定制。
streaming不會產生重複的header數據。
方案四:web socket
WebSocket和傳統的tcp socket連接相似,也是基於tcp協議,提供雙向的數據通道。WebSocket優勢在於提供了message的概念,比基於字節流的tcp socket使用更簡單,同時又提供了傳統的http所缺少的長連接功能。不過WebSocket相對較新,2010年才起草,並不是所有的瀏覽器都提供了支持。各大瀏覽器廠商最新的版本都提供了支持。
解決head of line blocking
Head of line blocking(以下簡稱為holb)是http2.0之前網絡體驗的最大禍源。健康的請求會被不健康的請求影響,而且這種體驗的損耗受網絡環境影響,出現隨機且難以監控。為了解決holb帶來的延遲,協議設計者設計了一種新的pipelining機制。
不過pipelining並不是救世主,它也存在不少缺陷:
- pipelining只能適用於http1.1,一般來說,支持http1.1的server都要求支持pipelining。
- 只有冪等的請求(GET,HEAD)能使用pipelining,非冪等請求比如POST不能使用,因為請求之間可能會存在先後依賴關係。
- head of line blocking並沒有完全得到解決,server的response還是要求依次返回,遵循FIFO(first in first out)原則。也就是說如果請求1的response沒有回來,2,3,4,5的response也不會被送回來。
- 絕大部分的http代理服務器不支持pipelining。
- 和不支持pipelining的老服務器協商有問題。
- 可能會導致新的Front of queue blocking問題。
正是因為有這麼多的問題,各大瀏覽器廠商要么是根本就不支持pipelining,要么就是默認關掉了pipelining機制,而且啟用的條件十分苛刻。可以參考chrome對於pipeling的問題描述。
HTTP2的優勢
二進制分幀
http1.x誕生的時候是明文協議,其格式由三部分組成:start line(request line或者status line),header,body。要識別這3部分就要做協議解析,http1.x的解析是基於文本。基於文本協議的格式解析存在天然缺陷,文本的表現形式有多樣性,要做到健壯性考慮的場景必然很多,二進制則不同,只認0和1的組合。基於這種考慮http2.0的協議解析決定採用二進制格式,實現方便且健壯。
http2.0用binary格式定義了一個一個的frame,和http1.x的格式對比如下圖:
http2.0的格式定義更接近tcp層的方式,這張二機制的方式十分高效且精簡。
- length定義了整個frame的開始到結束
- type定義frame的類型(一共10種)
- flags用bit位定義一些重要的參數
- stream id用作流控制
- payload就是request的正文了
為什麼麼能在不改動HTTP/1.x 的語義、方法、狀態碼、URI 以及首部字段….. 的情況下, HTTP/2 是如何做到「突破HTTP1.1 的性能限制,改進傳輸性能,實現低延遲和高吞吐量」?
關鍵之一就是在應用層(HTTP/2)和傳輸層(TCP or UDP)之間增加一個二進制分幀層。
在二進制分幀層中, HTTP/2 會將所有傳輸的信息分割為更小的消息和幀(frame),並對它們採用二進制格式的編碼,其中HTTP1.x 的首部信息會被封裝到HEADER frame ,而相應的Request Body 則封裝到DATA frame 裡面。
HTTP/2 通信都在一個連接上完成,這個連接可以承載任意數量的雙向數據流。
在過去, HTTP性能優化的關鍵並不在於高帶寬,而是低延遲。TCP連接會隨著時間進行自我「調諧」,起初會限制連接的最大速度,如果數據成功傳輸,會隨著時間的推移提高傳輸的速度。這種調諧則被稱為TCP慢啟動。具體複習:《再深談TCP/IP三步握手&四步揮手原理及衍生問題—長文解剖IP》、《從網卡發送數據再談TCP/IP協議—網絡傳輸速度計算-網卡構造》
由於這種原因,讓原本就具有突發性和短時性的HTTP 連接變的十分低效。
HTTP/2 通過讓所有數據流共用同一個連接,可以更有效地使用TCP 連接,讓高帶寬也能真正的服務於HTTP 的性能提升。
總結:
- 單連接多資源的方式,減少服務端的鏈接壓力,內存佔用更少,連接吞吐量更大
- 由於TCP 連接的減少而使網絡擁塞狀況得以改善,同時慢啟動時間的減少,使擁塞和丟包恢復速度更快
多路復用(Multiplexing)||連接共享
多路復用允許同時通過單一的HTTP/2 連接發起多重的請求-響應消息。
眾所周知,在HTTP/1.1 協議中「瀏覽器客戶端在同一時間,針對同一域名下的請求有一定數量限制。超過限制數目的請求會被阻塞」。
Clients that use persistent connections SHOULD limit the number of simultaneous connections that they maintain to a given server. A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy. A proxy SHOULD use up to 2*N connections to another server or proxy, where N is the number of simultaneously active users. These guidelines are intended to improve HTTP response times and avoid congestion.
source:RFC-2616-8.1.4 Practical Considerations
比如TCP建立連接時三次握手有1.5個RTT(round-trip time)的延遲,為了避免每次請求的都經歷握手帶來的延遲,應用層會選擇不同策略的http長鏈接方案。又比如TCP在建立連接的初期有慢啟動(slow start)的特性,所以連接的重用總是比新建連接性能要好。
下圖總結了不同瀏覽器對該限制的數目。
來源: Roundup on Parallel Connections
這也是為何一些站點會有多個靜態資源CDN 域名的原因之一
上面協議解析中提到的stream id就是用作連接共享機制的:
一個request對應一個stream並分配一個id,這樣一個連接上可以有多個stream,每個stream的frame可以隨機的混雜在一起,接收方可以根據stream id將frame再歸屬到各自不同的request裡面。因而HTTP/2能多路復用(Multiplexing) ,允許同時通過單一的HTTP/2連接發起多重的請求-響應消息。
因此HTTP/2可以很容易的去實現多流並行而不用依賴建立多個TCP連接,HTTP/2把HTTP協議通信的基本單位縮小為一個一個的幀,這些幀對應著邏輯流中的消息。並行地在同一個TCP連接上雙向交換消息。
前面還提到過連接共享之後,需要優先級和請求依賴的機製配合才能解決關鍵請求被阻塞的問題。http2.0裡的每個stream都可以設置又優先級(Priority)和依賴(Dependency)。優先級高的stream會被server優先處理和返回給客戶端,stream還可以依賴其它的sub streams。優先級和依賴都是可以動態調整的。動態調整在有些場景下很有用,假想用戶在用你的app瀏覽商品的時候,快速的滑動到了商品列表的底部,但前面的請求先發出,如果不把後面的請求優先級設高,用戶當前瀏覽的圖片要到最後才能下載完成,顯然體驗沒有設置優先級好。同理依賴在有些場景下也有妙用。
首部壓縮(Header Compression)
http1.x的header由於cookie和user agent很容易膨脹,而且每次都要重複發送。
HTTP/1.1並不支持HTTP 首部壓縮,為此SPDY 和HTTP/2 應運而生
這里普及一個小知識點。現在大家都知道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的變化。
現在SPDY使用的是通用的DEFLATE 算法,而HTTP/2則使用了專門為首部壓縮而設計的 HPACK 算法。
http2.0使用encoder來減少需要傳輸的header大小,通訊雙方各自cache一份header fields表,既避免了重複header的傳輸,又減小了需要傳輸的大小。高效的壓縮算法可以很大的壓縮header,減少發送包的數量從而降低延遲。
服務端推送(Server Push)
服務端推送是一種在客戶端請求之前發送數據的機制。在HTTP/2 中,服務器可以對客戶端的一個請求發送多個響應。Server Push 讓HTTP1.x 時代使用內嵌資源的優化手段變得沒有意義;如果一個請求是由你的主頁發起的,服務器很可能會響應主頁內容、logo 以及樣式表,因為它知道客戶端會用到這些東西。這相當於在一個HTML 文檔內集合了所有的資源,不過與之相比,服務器推送還有一個很大的優勢:可以緩存!也讓在遵循同源的情況下,不同頁面之間可以共享緩存資源成為可能。
http2.0引入RST_STREAM類型的frame,可以在不斷開連接的前提下取消某個request的stream,表現更好。
重置連接表現更好
很多app客戶端都有取消圖片下載的功能場景,對於http1.x來說,是通過設置tcp segment裡的reset flag來通知對端關閉連接的。這種方式會直接斷開連接,下次再發請求就必須重新建立連接。http2.0引入RST_STREAM類型的frame,可以在不斷開連接的前提下取消某個request的stream,表現更好。
流量控制(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的部署出了問題。
更安全的SSL
HTTP2.0使用了tls的拓展ALPN來做協議升級,除此之外加密這塊還有一個改動,HTTP2.0對tls的安全性做了近一步加強,通過黑名單機制禁用了幾百種不再安全的加密算法,一些加密算法可能還在被繼續使用。如果在ssl協商過程當中,客戶端和server的cipher suite沒有交集,直接就會導致協商失敗,從而請求失敗。在server端部署http2.0的時候要特別注意這一點。
關於HTTP/2 的Server Push 以及HTTP/2 的緩存策略
典型問題:
「如果客戶端早已在緩存中有了一份copy 怎麼辦?」還要Push 嗎?
詳情參考另一個答案:
HTTP/2 對現在的網頁訪問,有什麼大的優化呢?體現在什麼地方
PS:
強烈推薦閱讀Mark Nottingham在 Velocity Beijing 2015 的speech:HTTP/2 for Front-End Developers ,關於HTTP/2下的前端性能優化相關。
Slide地址:HTTP/2 for Front-End Developers
按照OSI網絡分層模型,IP是網絡層協議,TCP是傳輸層協議,而HTTP是應用層的協議。在這三者之間,SPDY和WebSocket都是與HTTP相關的協議,而TCP是HTTP底層的協議。
HTTP2的發展歷史
一、http
HTTP協議經過多年的使用,發現了一些不足,主要是性能方面的,包括:
- HTTP的連接問題,HTTP客戶端和服務器之間的交互是採用請求/應答模式,在客戶端請求時,會建立一個HTTP連接,然後發送請求消息,服務端給出應答消息,然後連接就關閉了。(後來的HTTP1.1支持持久連接)
- 因為TCP連接的建立過程是有開銷的,如果使用了SSL/TLS開銷就更大。
- 在瀏覽器裡,一個網頁包含許多資源,包括HTML,CSS,JavaScript,圖片等等,這樣在加載一個網頁時要同時打開連接到同一服務器的多個連接。
- HTTP消息頭問題,現在的客戶端會發送大量的HTTP消息頭,由於一個網頁可能需要50-100個請求,就會有相當大的消息頭的數據量。
- HTTP通信方式問題,HTTP的請求/應答方式的會話都是客戶端發起的,缺乏服務器通知客戶端的機制,在需要通知的場景,如聊天室,遊戲,客戶端應用需要不斷地輪詢服務器。
而SPDY和WebSocket是從不同的角度來解決這些不足中的一部分。除了這兩個技術,還有其他技術也在針對這些不足提出改進。
二、SPDY
SPDY的主要目的是減少50%以上的頁面加載時間,但是呢不增加部署的複雜性,不影響客戶端和服務端的Web應用,只需要瀏覽器和Web服務器支持SPDY。主要有以下幾:
- 多路復用,一個TCP連接上同時跑多個HTTP請求。請求可設定優先級。
- 去除不需要的HTTP頭,壓縮HTTP頭,以減少需要的網絡帶寬。
- 使用了SSL作為傳輸協議提供數據安全。
- 對傳輸的數據使用gzip進行壓縮
- 提供服務方發起通信,並向客戶端推送數據的機制。
實質上,SPDY就是想不影響HTTP語義的情況下,替換HTTP底層傳輸的協議來加快頁面加載時間。
SPDY的解決辦法就是設計了一個會話層協議–幀協議,解決多路復用,優先級等問題,然後在其上實現了HTTP的語義。
SPDY的誕生和表現說明了兩件事情:一是在現有互聯網設施基礎和http協議廣泛使用的前提下,是可以通過修改協議層來優化http1.x的。二是針對http1.x的修改確實效果明顯而且業界反饋很好。正是這兩點讓IETF(Internet Enginerring Task Force)開始正式考慮制定HTTP2.0的計劃,最後決定以SPDY/3為藍圖起草HTTP2.0,SPDY的部分設計人員也被邀請參與了HTTP2.0的設計。
三、WebSocket
WebSocket則提供使用一個TCP連接進行雙向通訊的機制,包括網絡協議和API,以取代網頁和服務器採用HTTP輪詢進行雙向通訊的機制。
本質上來說,WebSocket是不限於HTTP協議的,但是由於現存大量的HTTP基礎設施,代理,過濾,身份認證等等,WebSocket借用HTTP和HTTPS的端口。
由於使用HTTP的端口,因此TCP連接建立後的握手消息是基於HTTP的,由服務器判斷這是一個HTTP協議,還是WebSocket協議。WebSocket連接除了建立和關閉時的握手,數據傳輸和HTTP沒丁點關係了。
WebSocket也有自己一套幀協議。
四、SPDY和WebSocket的關係
SPDY和WebSocket的關係比較複雜。
- 補充關係,二者側重點不同。SPDY更側重於給Web頁面的加載提速,而WebSocket更強調為Web應用提供一種雙向的通訊機制以及API。
- 競爭關係,二者解決的問題有交集,比如在服務器推送上SPDY和WebSocket都提供了方案。
- 承載關係,試想,如果SPDY的標準化早於WebSocket,WebSocket完全可以側重於API,利用SPDY的幀機制和多路復用機制實現該API。Google提出草案,說WebSocket可以跑在SPDY之上。WebSocket的連接建立在SPDY的流之上,將WebSocket的幀映射到SPDY的幀上。
- 融合關係,如微軟在HTTP Speed+Mobility中所做的。
http2的競爭兄弟
1. HTTP Speed+Mobility
還有一個有趣的技術叫做HTTP Speed+Mobility,和SPDY一樣都是HTTP 2.0標準的競爭者,HTTP Speed+Mobility來自微軟。HTTP SM借鑒了SPDY和WebSocket的協議,將二者揉為一體,又有所取捨。
HTTP SM的設計原則包括:
- 保留HTTP的語義,這一點和SPDY一致,但也正應如此,拋棄了SPDY裡的ServerPush。
- 遵守分層的網絡架構,TCP能做的,HTTP SM不做,因此去除了SPDY的流控。
- 使用現有標準,因此使用HTTP/1.1 Upgrade header機制,借用了WebSocket的握手機制和幀格式(RFC6455)。
- 客戶端掌握內容的控制,因此不強制使用壓縮和SSL/TLS。
- 考慮到網絡的費用和電力,這點考慮到了移動設備以及物聯網,提供了Credit Control機制。
HTTP SM分以下幾層:
- 會話層和幀協議,這部分取自WebSocket協議。包括握手機制,以及幀格式。
- 流層(包括多路復用),這部分主要藉鑑SPDY,包括多路復用,流優先級,但增加了Credit Control。這部分作為WebSocket協議的擴展。
- HTTP層,在流層上實現HTTP語義,這部分也藉鑑自SPDY。
2. Network-Friendly HTTP
NF是HTTP 2.0候選方案之一,主要提出以下改進:
- 對HTTP頭的名稱進行二進制編碼
- 對通用HTTP頭進行分組
- 請求/應答的多路復用
- 分層模型
NF同樣定義了幀和流,
3. WAKA
WAKA也是HTTP 2.0候選方案之一,是HTTP協議原作者Roy Fielding提出的一個提案。
WAKA支持多路復用,支持優先級。WAKA提出了兩個新的HTTP方法,RENDER和MONITOR。
參考資料:
- Gitbook 《HTTP2講解》 by Calvin Zhang and Simon Xia:http2講解- GitBook
- HTTPS、SPDY以及HTTP/2性能簡單對比:A Simple Performance Comparison of HTTPS, SPDY and HTTP/2
- HTTP/2的壓縮算法–HPACK(RFC7541):HPACK: Header Compression for HTTP/2
- NGINX HTTP/2白皮書:https://www.nginx.com/wp-content/uploads/2015/09/NGINX_HTTP2_White_Paper_v4.pdf
- NGINX Blog–提升HTTP/2 性能的7個小建議:
- HTTP/2 for a Faster Web
- O’Reilly HTTP2-high-perf-browser-networking:http://www.oreilly.com/webops-perf/free/files/HTTP2-high-perf-browser-networking.pdf
- HTTP/2 新特性淺析:
- Kevin blog關於HTTP/2的系列歸檔:HTTP/2 |凱文叔叔的網誌
- Can i use上關於支持HTTP/2的瀏覽器:Can I use… Support tables for HTML5, CSS3, etc
- http://http2-explained.haxx.se/content/en/part5.html
- https://www.chromium.org/spdy/spdy-whitepape
本文主要內容來源:《HTTP 2.0的那些事》