HTTP2 的一些理解與練習
HTTP2 解決了部分浪費問題
本文只做科普,真正的學習請參考標準 RFC 7520 HTTP/2。
在我剛開始工作時,經常使用 SOAP 呼叫 WebService,理論上 SOAP 可以使用任何協議作為載體:HTTP、FTP、SMTP、甚至你自己寫的某種協議,但是大家都是使用 HTTP,這便是我第一個問題:why?答案當然可以寫出很多,但是有一個詞啟發了我,那就是穿牆。穿牆保證你可以很方便的通過防火牆去呼叫遠端的服務,那麼為什麼防火牆支援 80 或者 443 埠呢(in default most times)?因為 HTTP 協議很流行。那麼為什麼 HTTP 協議很流行呢?因為 web,沒有 web 你現在也沒辦法在簡書上看文章了。所以 HTTP 是最重要的應用層協議。但是 HTTP 是非常不美好的一個實現,最讓人詬病的是資源的浪費。
- 我們知道這是一個先進先出的協議 FIFO,也就是說在一個 connection 中,第一個 request 發出後你必須要等待 response,直到收到 response 再進行下一次的請求,所以現代瀏覽器會同時啟動多個 connection 進行資源下載(RFC-2616-8.1.4 Practical Considerations) 來提高效能。但是,在底層 socket 中,我們知道這是一個 duplex 的連線,那為什麼我們還要等前一個請求完成呢?
- 當你在淘寶進行一次搜尋時,如果 dump 出一個完整的 HTTP 請求,可以看到除了內容以外還有大量的 header 進行傳輸,特別是一些用於記錄行為的 cookie。我們知道 HTTP 一個無狀態的協議(這是一個優點大於缺點的選擇),所以我們不得不帶上這些 header 以及其中的 cookie,而 body 則是可以選擇壓縮來節約頻寬的。
- HTTP 是底層是建立在 TCP 之上的,我們知道如果傳輸資料的成本小與建立連線的成本(特別是 TLS)那是及不划算的,也無法利用到現在網路終端的高頻寬。之前我們的解決方案多是降低延遲,而不是增加頻寬使用效率,因為 HTTP 無法做到在一個連線上增加傳輸,無法多路複用(Multiplexing)。
- 當你開啟某大型網站時,HTML 下載完成後(現代瀏覽器不是這樣,但是效率也不會太高),你才能夠獲取內嵌的 js、css、圖片等等這些資源。但是幾乎每個網站的每個頁面都有共享的資源(例如 header、通用樣式等),或者我們希望在連線建立時就能獲取這些通用的資源。
- 對於 Mobile HTTP 是天生不友好的,WebService 開發相對於 RPC 是簡單的,但是對於效能有一定要求的 APP,使用 WebService 則有很大的風險,連線過多、效率很低。所以也有部分同學在自己的 APP 中使用自訂的協議(各種 PRC),而這些協議難以開發,難以除錯。
- Server 不能發起主動通告,我們有時候希望伺服器在做完某些事情後通知我們,但是 HTTP 是不支援的,所以我們有了很多折中的方案:while poll、websocket 等,但是我們知道,這是效率很低的做法。
HTTP2 怎麼做到的?
HTTP2 最大的優勢就是提升了 web 效能,並且相容了 HTTP1.1 的語義,實現了向後相容。Akamai 的朋友曾經告訴我,Akamai 在 CDN 中已經預設開啟了 HTTP2,大約效能提升在 10%~20%(這個數字是非常粗略的並且沒有足夠的上下文,很多情況需要具體分析,所以才會有最後的例子),而這對於很多開發人員來說,只是一個開關的事情。
所以 HTTP2 需要重新發明與 TCP 的整合方式,而不是簡單粗暴的將 ASCII 格式的訊息丟下去,同時還必須要相容 HTTP1.1,所以應對這種問題,經典的解決方案就是:加一層。
Any problem in computer science can be solved by another layer of indirection.
Binary Framing —— 二進位制封幀
HTTP2 引入了 frame 作為資料層的封裝表示,歸根結底HTTP2 的目標是提高資源利用——儘量的在一個 connection 中做更多的事情——也就意味著原始的 raw ASCII 傳遞是需要被替代的,而這個替代就是 frame 與 stream。Binary Frame 是 HTTP2 資料交換的單位或是最小實體,我們使用二進位制的格式去構造這些 frame。操作 frame 要簡單于一個巨大的二進位制流,這意味著我們可以做很多高階的操作:併發、流量控制、錯誤處理與優先排序,同時還有更好的擴充套件性。
Multiplexing —— 多路複用
HTTP2 最重要的改進就是多路複用,為了實現多路複用,引入了 stream 這個抽象的概念。如果 frame 是車廂的話,stream 就是火車,排程這些 stream 來達到多路複用(老實說,要按照標準實現 stream 來達到多路複用真是一個很有挑戰的事情,特別是我看完了 stream state 後……或許應該用 Go 寫一個作為練習)。按照 RFC,stream 應該有一下的特性:
- Stream 是獨立的、雙向的用於 frame 傳輸的序列
- 一個 HTTP2 connection 包含大量的、併發的 stream
- 通訊雙方可以從 stream 中獲取 frame
- Stream 在被建立後可以單獨的使用,也可以被共享
- Stream 可以被通訊雙方關閉
- Stream 中的 frame 的順序是非常重要的,應答 frame 的順序與收到時相同
- Stream 使用 Integer 值標識,並由端點指定
所以我們可以訪問一個網站,就使用一個 connection,也不會擔心 FIFO 造成效能上的損失(head-of-line blocking problem),如果一個 response 比較慢,其他的 frames 會帶來另一些快速的 response,而系統不會被 block 住。
Header Compression —— 頭部壓縮
很多人喜歡用 TCP Slow Start 來說明 Header Compression 的作用,我感覺貌似搞錯了點。雖然是有一部分的相關性,但是重點是為了節約傳輸的 bits。即時輕度的頭部壓縮,只要能在一次 round trip 完成任務就是非常高效的了。很多網站都有很多圖片、樣式表、JS等等資源,如果每個資源有 800 bytes 的 headers(cookies、refer 等等),我們可以計算下只為這些 header 傳輸,我們可以節約多少資源。
Server Push —— 服務端推送
當瀏覽器請求一個頁面時,伺服器會在響應中傳送 HTML 返回給瀏覽器,然後需要等待瀏覽器解析 HTML 並在開始傳送JavaScript,影像和 CSS 之前發出對所有嵌入資源的請求。Server Push 可能允許伺服器通過將它認為客戶端需要的響應 push 到其快取中來避免這種延遲,簡直太智慧、太美好了!所以在 2018 年我們終於有點未來世界的影子了?
然而並不是,仔細想想 Server Push 的描述,有幾個詞是很難做到的:“需要的”、“快取”。有快取,就需要有過期策略,就需要有相應的重新整理策略,沒有哪個前端工程師喜歡快取,當然大多數人必須承認快取是能提高部分使用者體驗的。
但是我一直比較關心的是,Server Push 可以替代一些只為了推送而開的 WebSocket 與 loop AJAX 嗎?當然如果是富互動應用,我不覺得 HTTP2 的目標是取代 WebSocket。
練習
粗略的讀完了 HTTP2 的 RFC,也做了一些簡單的練習,主要使用者觀察多路複用的具體表現與 Server Push,思路是使用 docker-compose 在本地開啟一些 NGINX 容器。使用 docker 去驗證、學習是一個很好的載體,某種情況下,docker 是一個秒級的、輕量的、全功能的虛擬機器。而 compose 可以避免你記錄一堆命令或者配置。
在 docker-compose.yml
檔案中我們定義了三個容器用來驗證,web-server-http1
用於對照。
version: '2'
services:
base:
image: nginx:1.13.12
volumes:
- ./site:/usr/share/nginx/html
- ./cert:/etc/nginx/ssl
web-server-http1:
extends:
service: base
volumes:
- ./config/base-ssl.conf:/etc/nginx/nginx.conf
ports:
- "443:443"
web-server-http2:
extends:
service: base
volumes:
- ./config/base-ssl-http2.conf:/etc/nginx/nginx.conf
ports:
- "444:443"
web-server-http2-server-push:
extends:
service: base
volumes:
- ./config/base-ssl-http2-server-push.conf:/etc/nginx/nginx.conf
ports:
- "445:443"
以下是多路複用的觀察結果:
關於 NGINX 的 HTTP2 Features 可以參考:
相關文章
- Http2的一些特點HTTP
- Python面試的一些心得,與Python練習題分享Python面試
- 對於http/http2的一些總結HTTP
- python3的一些練習題Python
- Flutter | 一些很適合新手練習的DemoFlutter
- 一些簡單的程式設計練習題程式設計
- mysql練習 —— 關於一些函式的使用MySql函式
- 記錄 http2 四個難以理解的疑惑點HTTP
- http2與http1HTTP
- SparkSql與Redis綜合練習SparkSQLRedis
- kafka-一些我在學習中的理解Kafka
- 課時31.無序列表練習(理解)
- 8.C語言的一些練習題坑整理C語言
- 關於排列熵的一些理解與解釋熵
- 練習負載均衡時遇到的一些問題,求解答負載
- 關於Netty的一些理解、實踐與陷阱Netty
- 面向資料庫與物件導向的一些理解資料庫物件
- RunLoop的一些學習與總結OOP
- Flex 佈局:個人的學習與理解Flex
- 適合 Kubernetes 初學者的一些實戰練習 (三)
- 適合 Kubernetes 初學者的一些實戰練習 (四)
- 適合 Kubernetes 初學者的一些實戰練習(二)
- 適合 Kubernetes 初學者的一些實戰練習(一)
- 適合 Kubernetes 初學者的一些實戰練習 (五)
- 適合 Kubernetes 初學者的一些實戰練習 (六)
- HTTP2基本概念學習筆記HTTP筆記
- 對CSS vertical-align的一些理解與認識CSS
- JSON的一些理解JSON
- orion使用的一些理解
- 與遊戲世界互動-作業與練習(5)遊戲
- 遞迴與分治演算法練習遞迴演算法
- 控制結構與函式練習(一)函式
- 控制結構與函式練習(二)函式
- 控制結構與函式練習(三)函式
- http2HTTP
- Jetty的http2模組JettyHTTP
- 自訓練 + 預訓練 = 更好的自然語言理解模型模型
- 分散式鎖的一些理解分散式