HTTP/1.1 的隊頭阻塞
問題:HTTP/1.1 是一個純文本協議,它只在有效荷載(payload)的前面附加頭(headers),在資源塊(resource chunks)之間不使用分隔符。它不會進一步區分單個資源與其他資源。HTTP 規定報文必須是“一發一收”,這就形成了一個先進先出的串行隊列。
比如:當瀏覽器發送給服務器的資源包括:js(大資源塊)、css(小資源塊)等內容,但是服務器不能對他們進行分塊解析,就會導致需要等到js塊解析完畢后,才去解析css塊。稱為http隊頭阻塞。
一種解決辦法,瀏覽器打開許多並行TCP連接,但這既不高效,也不可擴展。
HTTP/2(基於 TCP)的隊頭阻塞
目標:回到單個 TCP 連接,正確地復用資源塊,解決http隊頭阻塞問題。
解決方案:在資源塊之前添加了數據幀(DATA frame),標識每個資源塊屬於哪個“流”(stream)。這些數據幀主要包含兩個關鍵的元數據。首先:下面的塊屬於哪個資源。每個資源的“字節流(bytestream)”都被分配了一個唯一的數字,即流id(stream id)。第二:塊的大小是多少。
這樣報文到達服務端之后,服務端應用可以區分哪些屬於同一個資源快,進而可以進行復用。即:解決了“應用層”隊頭阻塞。
另外的問題還有”tcp層“隊頭阻塞:
假設包3先到達,服務器端必須等待包2到達后,才將其和包3送給瀏覽器。(HTTP/2 主要的問題在於,多個 HTTP 請求在復用一個 TCP 連接,下層的 TCP 協議是不知道有多少個 HTTP 請求的。所以一旦發生了丟包現象,就會觸發 TCP 的重傳機制,這樣在一個 TCP 連接中的所有的 HTTP請求都必須等待這個丟了的包被重傳回來)。
即:如果一個 TCP 包丟失,所有后續的包都需要等待它的重傳,即使它們包含來自不同流的無關聯數據。
和http1.1主要區別:
二進制傳輸:
在HTTP2.0中引入了新的編碼機制,所有傳輸的數據都會被分割,並采用二進制格式編碼。區別於基於文本的方式傳輸。
多路復用:
幀是最小的數據單位,每個幀會標識出該幀屬於哪個流,流是多個幀組成的數據流。所謂多路復用,即在一個TCP連接中存在多個流,即可以同時發送多個請求。
HTTP/3(基於 QUIC)的隊頭阻塞
由於tcp本身的限制,難以對其進行改變,使其具有”流“的意識。
選擇的替代方法是以 QUIC 的形式實現一個全新的傳輸層協議。它運行在不可靠的 UDP 協議之上。但它包括 TCP 的所有特性(可靠性、擁塞控制、流量控制、排序等),且集成了TLS,不允許未加密的連接。故創建了 HTTP/3,運行在QUIC協議上。
QUIC 的流幀(STREAM frames)分別跟蹤每個流的字節范圍。
QUIC協議知道有自己獨立的流。
比如:當服務器端的包3比包2早到達,QUIC查看流1的字節范圍,發現這個流幀(STREAM frame)完全遵循流id 1的第一個流幀,它可以立即將這些數據提供給瀏覽器進行處理。然而,對於流id 2,QUIC確實看到了一個缺口(它還沒有接收到字節0-299,這些字節在丟失的 QUIC 數據包2中)。它將保存該流幀(STREAM frame),直到 QUIC 數據包2的重傳(retransmission)到達。
不太深入的來看,QUIC 確實解決了 TCP 的隊頭阻塞。
補充
http1.x采用長連接(Connection:keep-alive),可以在一個TCP請求上,發送多個http請求。
有非管道化和管道化,兩種方式:
非管道化:完全串行執行,請求->響應->請求->響應…,后一個請求必須在前一個響應之后發送。
管道化:請求可以並行發出,但是響應必須串行返回。后一個響應必須在前一個響應之后。原因是,沒有序號標明順序,只能串行接收。即客戶端可以並行,服務端串行。客戶端可以不用等待前一個請求返回,發送請求,但服務器端必須順序的返回客戶端的請求響應結果。