Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
服務是一種流行的設計模式,其中一個大型應用程序被分解為多個獨立且松散耦合的服務,這些服務通過預定義的接口相互通信;Walmart 的ML平臺使用相同的原理構建: 部署在 Kubernetes 集群中的獨立服務通過 REST API 進行通信。
作為平臺功能,為事件提供以用戶為目標的通知是一項優先要求。為此,我們開發了一個模型框架,可供任何對通知服務感興趣的基于微服務的應用程序使用。
在較高級別上,系統應該能夠根據以下規則生成和處理通知。
除了所需的規則外,我們還向系統添加了一些所需的功能:
我們將整個系統設計為一個基于 Java 的庫,可以將其導入任何有興趣發送通知的微服務中。對于通知存儲,Redis 是首選,服務器發送事件 (SSE) 用于將通知發送到 UI 客戶端(用戶的瀏覽器)。我們將在后續部分中分別介紹每個系統,然后將它們放在一起,看看它們是如何加起來完成此功能的。
后端實現
1. 通知模型
通知結構的一個非常簡單的設計需要兩個字段——目標用戶和消息。這是我們最初采用的結構,隨著功能開始成熟,添加了更多字段以增強界面并在數據結構中捆綁更多信息。最后,我們正式確定了這種通知結構。
|
2. 存儲通知
在 Notification Store 中存儲通知有兩個要點需要考慮:
作為鍵值存儲的 Redis 以毫秒級的延遲完成了這兩項任務。此外,Redis 已被證明具有故障恢復能力和高度可擴展性。因此,它被選為通知的后備存儲。
此外,Redis 具有對更高 ADT 的內置支持,例如列表和映射。我們利用映射(即 Redis 中的哈希)來存儲通知。對于每個用戶,存儲通知 JSON 的相應通知 id 哈希值。每個唯一的用戶 ID 都充當 Redis 中的一個鍵。這種結構確保支持系統中的 232 個用戶,每個用戶都有 232 個潛在的通知。
Redis 可以以線程安全的方式支持高并發工作負載。它還可以通過 Redis Sentinels 提供生產級支持以實現高可用性和 AOF 文件備份以實現持久性。有關于如何設置它們以運行生產級 Redis 集群的優秀文檔(請參閱Redis Sentinel和Redis Persistence)。
3.推送通知到存儲
通知庫公開了NotifierClient具有notifyUsers和notifyGroup方法的接口。要觸發通知,微服務將notifyUsers使用Notification對象和要向其發送通知的用戶 ID 列表調用該方法。該庫還允許創建可用于將相似用戶(例如,特定項目的所有用戶、使用 GPU 的所有用戶等)聚集在一起的組,并且微服務可以選擇使用notifyGroup方法向整個組發送通知。
Jedis是 Java 中最著名的與 Redis 通信的庫,我們在庫中使用它來讀寫通知。Jedis 支持 Sentinel 支持和連接池等高級功能,這也使其成為生產服務器的理想選擇。
為了防止讀取和寫入偶爾中斷,對 Redis 的調用通過Resilience4J 進行包裝,以確保在出現臨時故障時進行正確的重試和錯誤處理。
前端實現
為了啟用前端,我們使用 Express 服務器作為用戶瀏覽器和后端微服務之間的中間件來執行身份驗證和會話管理。我們搭載在這個服務器上從 Redis 讀取通知并將它們推送給用戶。
1. 將通知推送到瀏覽器
服務器發送事件 (SSE) 是一種建立在 HTTP 之上的技術。對于登錄到系統的每個用戶,我們在用戶會話期間建立一個持久的 HTTP 連接。SSE 的協議規范規定將 JSON 數據轉換為字符串,并且每個事件以兩個換行符結尾。
建立連接后,我們利用 Node.js 事件模型在 Redis 提供新通知時將數據推送到客戶端。我們發出一個通知事件,該事件附加到 HTTP SSE 處理程序范圍內的事件偵聽器。我們使用登錄用戶的唯一 id 將來自 Redis 的消息與用戶的連接進行匹配。
這個GitHub 代碼片段描述了它是如何完成的。
2.在瀏覽器中接收通知
在客戶端,SSE 提供了一個 EventSource API,允許我們連接到服務器并從它接收更新。SSE 有一個限制,它可以同時支持六個并發連接。由于我們在每個瀏覽器選項卡中打開一個新連接,因此它限制了我們的用戶一次只能打開六個選項卡。為了規避這個限制,我們使用SharedWorkers。這使我們能夠在 SharedWorker 中創建一個持久連接,并通過不同的瀏覽器選項卡和 iframe 訪問它。
共享工作者SharedWorker的一個缺點是它在 Safari 和 IE 上不受支持,但由于我們的大多數用戶群都在 Chrome 和 Firefox 上,因此這被視為可接受的解決方案。如果用戶使用 IE 或 Safari,我們將退回到僅允許 6 個選項卡的 SSE 模型。
當用戶第一次登錄時,會實例化一個新的共享工作者實例,然后將其附加到窗口實例。然后可以在所有瀏覽器上下文中訪問它。然后,網頁可以使用 MessagePort 對象與共享 worker 通信,并附加一個事件處理程序,每次共享 worker 推送消息時都會調用該處理程序。
github中的以下代碼段具有在瀏覽器中接收通知的代碼。
3. 向用戶顯示通知
每次打開新選項卡或瀏覽器窗口時,共享工作者都會為每個新選項卡分配一個端口號。這些端口號為每個用戶保存在一個數組中。每當生成新通知時,都會將其推送到所有端口,以使其在選項卡之間保持一致。關閉選項卡時beforeUnload會觸發一個事件,在該事件中我們從陣列中刪除相應的端口。
為了僅當用戶在選項卡上處于活動狀態時才顯示通知,我們處理visibilitychange由Page Visibility API公開的事件。處理程序將頁面標記為非隱藏,然后使用來自后端的通知刷新 redux 存儲。這會觸發 UI 的渲染,并且通知顯示在小吃欄中。
橋接后端和前端
系統的兩個部分協同工作以提供整個通知系統:
對于實時場景,新的通知一生成就需要通知 UI。我們使用 Redis PubSub 創建從后端服務到 UI 服務器的反饋通道,然后如所述通過 HTML5 SSE 與 UI 客戶端(或用戶的瀏覽器)進行通信。
當生成通知并將通知寫入用戶的密鑰時,庫還會在特定頻道上生成一條 PubSub 消息,其中相應的用戶 ID 已被修改。UI 服務器訂閱給定的 PubSub 頻道,并在接收到用戶 ID 時在其內存中構建通知映射。如果用戶在線,UI 服務器會在該用戶的 SSE 套接字上發送整個通知 JSON 映射,以在他的瀏覽器中呈現它。
下面給出了一個粗略的序列圖,用于演示通知實例從在后端服務中生成到向用戶顯示的流程。
一旦用戶響應通知(閱讀、點擊或刪除它),該信息必須流經后端存儲。我們將其實現為 REST 端點。
該庫本身公開了一個 Java API,它可以采用唯一的通知 id、用戶 id 和要更新的狀態,并且它將用更新的狀態修補 Redis 中的通知。然后可以使用服務將這個 API 與 REST 或任何其他類似(例如 gRPC)端點包裝在一起。
為了清除 Redis 中過期和刪除的通知,庫內部使用了Quartz調度程序。為了確保一次只運行一個清潔器實例,使用Redlock 算法來創建分布式鎖定機制。
整個框架可在https://github.com/daichi-m/notification4J 上作為庫使用。
天項目經理交給我一個開發任務。如果有人在前臺下了訂單就給后臺倉庫管理一個發貨通知。也就是服務端觸發一個事件,推送消息到客戶端。如果我用websocket來做還要搞個websocket服務器,而且還 有不少配置。websocket是全雙工通信,單向通信簡直是殺雞用牛刀。用輪詢吧,浪費服務器資源不說,還不一定實時,訂單處理慢了豈不是怠慢了客戶。有沒有別的選擇呢?當然有!
1. SSE推送技術
SSE全稱Server-sent Events,是HTML 5 規范的一個組成部分,具體去MDN網站查看相關文檔。該規范十分簡單,主要由兩個部分組成:第一個部分是服務器端與瀏覽器端之間的通訊協議,第二部分是在瀏覽器端可供 JavaScript 使用的 EventSource 對象。通訊協議是基于純文本的簡單協議。服務器響應的內容類型是“text/event-stream”。響應文本的內容可以看成是一個事件流,由不同的事件所組成。每個事件由類型和數據兩部分組成,同時每個事件可以有一個可選的標識符。不同事件的內容之間通過僅包含回車符和換行符的空行(“\r\n”)來分隔。每個事件的數據可能由多行組成。
如上圖所示,每個事件之間通過空行來分隔。每一行都是由鍵值對組成。如果鍵為空則表示該行為注釋,會在處理時被忽略。例如第10行。
第1行表示一個只包含數據的事件。會按照默認事件走(message事件)。第3-4行代表一個附帶eventID的事件。第6-8行代表一個自定義事件。第10-14行代表一個多行數據事件,多行數據由換行符鏈接
key定義有以下幾種:
SSE只適用于高級瀏覽器,但是注意IE不直接支持。IE上的XMLHttpRequest對象不支持獲取部分的響應內容,所以不支持。每次總有IE,怪不得快被淘汰了。
2. SSE VS Websocket
2. SSE VS Websocket
3. Spring Mvc中的SSE
Spring Mvc對SSE進行了支持。如果你要聲明一個SSE連接。只需要在你的控制器聲明一個如下接口:
必須必須返回SseEmitter對象,SseEmitter對象是Session級別的,如果你要點對點針對每個session要獨立存儲。如果你是廣播可以共用一個SseEmitter對象。按照SSE規范也必須聲明produces為"text/event-stream"。當你調用該接口的時候將建立起SSE連接。
你可以在另一個線程中調用SseEmitter的send方法向客戶端發送事件。你也可以在發送事件后調用complete方法來關閉SSE連接。
4. 瀏覽器端的EventSource
由于SSE 是HTML5規范。所以對于APP端必須有HTML才能支持。并且IE如果要支持需要使用一些兼容開發包,比如polyfill庫。客戶端因為只接受事件所以開發比較簡單:
總結
今天介紹了SSE 服務端推送。和長輪訓、comet、websocket相比而言比較輕量級。在一些需要服務器實時推送規模不大的業務場景實現更簡單點。相信看了本文后你會很快入門。在實際開發中要根據業務對這幾種推送進行技術選型。沒有最好的只有最適合的。SSE對大多數開發者來說不夠熟悉。
文章來源:https://dwz.cn/vQHsUK6H
作者:碼農小胖哥
接上文HTTP請求的詳細過程
http協議版本歷史
http造就了萬維網,http成就了互聯網第三次信息技術革命并且影響著即將到來的第四次人工智能技術革命。
1989年第一個http協議,http0.9發布,發明了萬維網,創建了世界第一個網頁瀏覽器;
http1.0版本是在1996年正式發版的 ,這個時候才豐富了交互響應的文件類型,96年之前網頁是不能播放音樂的;
97年發布http1.1版本,2015年才發布http2.0版本,源自谷歌的SPDY協議,目前現代瀏覽器完全支持http2.0,http2可以更好的表達設備的在線狀態;
2022年6月,發布了http3 ,使用QUIC協議替代了TCP協議,作為位于應用層的通用傳輸協議,這是一種基于UDP構建的多路復用傳輸協議;
http3.0版本解決了TCP的一些問題,提高效率并保留了TCP的所有優點。
http2和http3這2個協議版本都是由谷歌主導的,并且第一個在chrome瀏覽器率先實現的;
http0.9到1.0版本見證了web1.0時代的開始,http1.1-2.0版本見證了web1.0并完成了它的使命和見證了web2.0時代發展到了今天。
這是開放式系統互聯的OSI七層模型圖,http協議位于應用程序層,需要經過傳輸層才能進行通信。
理論上傳輸層用哪個協議并不是那么重要,但一定要知道傳輸層的TCP和UDP兩個協議到底是什么,著重介紹傳輸層的原因是因為它對http協議至關重要,后面的IP層和其他底層對TCP和UDP都很友好,而且對http各個版本的發展并沒有影響。
TCP協議即傳輸控制協議,首先通過三次握手建立連接,然后傳輸數據,數據傳輸完畢,進入四次揮手關閉連接,這中間出現了數據丟失,則會重試保證數據傳輸可靠性。
可以看出TCP是一個面向連接的可靠的傳輸協議,而UDP協議是用戶數據協議,是面向數據報文的,發出去就發出去了,發不出去就發不出去,發丟了消息也不會去重試,UDP不需要握手建立連接,發送消息的數據又小,所以速度更快。
為了保證傳輸的可靠性,http0.9到2.0版本都是采用tcp協議傳輸,到了3.0版本就拋棄了tcp改用udp傳輸,利用QUIC協議在應用層保證數據傳輸的可靠性;
http0.9是萬維網第一個http協議,經過三次握手建立連接,客戶端發起get請求,服務器僅能響應http文本類型的數據,但不能在客戶端顯示圖片和播放音樂,客戶端收到數據之后,四次揮手關閉連接,所以也叫單線協議,同時http0.9版本沒有http標頭,沒有狀態碼、錯誤碼,
如圖所示僅僅使用一行請求信息,所以也叫它單行版本,就是因為0.9版本太簡陋,服務器僅能響應html文件,比如制作個人網頁,
于是就有了http1.0,請求加入了http協議標頭,響應加了狀態碼和傳輸的內容類型,
也就是經常在服務器配置的MIME媒體類型,即多用途互聯網郵件類型。
不為http服務器配置MIME類型,服務器就沒辦法解釋這個內容類型是個啥,客戶端也不認識就會出現問題,服務器配置好以后,就可以傳輸更多類型的文件內容數據了。
和0.9版本一樣,1.0版本還是單線協議,一個連接只能處理一個請求一個響應,每次建立物理連接的成本很高,
并且服務器遭受不住太多客戶端訪問,否則服務器扛不住就會直接給你報503錯誤,為了減輕服務器壓力,出現了升級的http1.1版本,該版本中引入了一個重要的概念,就是持久連接的概念,
即增加了keep-alive值的標頭,就意味著建立連接后,指定時間內不會關閉這個連接,可以在保持的這個連接上發送多個請求,并且不會出現一個往返就關閉連接的情況,
節省了每次交互都會握手和揮手的時間,除此之外1.1版本還引入了流水線概念,在服務器響應前,客戶端可以一次發送多個請求,服務器收到多個請求后,按順序將準備好的數據再一個一個的回復給客戶端,對比一下左邊一次一個請求一個響應和右邊一次多個請求逐個響應的過程,可以看到明顯的節約了請求發送的時間,提高了傳輸的效率。
按順序響應有沒有問題?
首先就會造成一個重要的問題,head of line blocking(線頭阻塞即流水線頭阻塞),
假如客戶端處理了三個請求,第一個請求處理的時間太長,第二個第三個請求是不是得必須老實的排隊,等待第一個請求處理完成呢?
這就造成了流水線的塞車現象,在這個版本中解決塞車的辦法就是瀏覽器通常會保持6個左右的連接以緩解一個連接塞車,客戶端可以通過其他連接繼續發送請求。
在連續發送請求的情況下,每次請求總是會帶上相同基本不會變化的http標頭,并且http1.1發送的數據還是純文本的,傳輸數據不但大還啰嗦。
http2協議
以上2個問題就是http2要解決的,http2協議是http1.1版本的擴展而不是替換,因為它只是更好的解決了1.1版本的問題和功能補充,
b站網絡請求就是使用的http2協議,
http2在應用層改變了傳輸方式,之前版本應用程序層傳遞的http標頭和數據都是明文的,
http2在應用層增加了一個二進制幀數據處理層,
將http標頭和數據明文消息拆分成二進制數據幀進行傳輸,
每個數據幀包含自己攜帶的數據并用stream id做標識,這也解釋了什么叫數據流,就是將數據切開形成數據幀,然后將它們單獨或者打包成獨立流傳輸到目的地后,
再按照stream id將它們組裝起來還原成原始數據。
好處就是OSI模型應用程序下的底層協議,更喜歡二進制數據幀,因為它們的大部分工作被應用程序層做了,它們的活少了,響應的網絡傳輸速度也就提高了。
另一個好處就是再大的文件也給你剁的細碎,規避了大文件傳輸大小的限制,比如IIS、apache等http服務器都有對單個請求體又大小的限制。
客戶端以流的方式請求,服務器以流的形式響應,就形成了雙向數據流,數據幀都用stream id做標識,然后stream id將多個數據幀識別為一個消息,
保證了數據的完整性。
http2協議是客戶端與服務器建立一個連接,再加上雙向數據流,客戶端和服務器的交互就可以重用它們建立的單個連接傳遞數據,就形成了http2的多路復用。
既然數據幀標明了stream id,那么傳輸數據流的順序可以是亂序的,當然傳輸數據也可以設置優先權重的,
無論是客戶端還是服器接收到的流數據先到的在緩沖區先組裝,不用等待前一個請求數據處理完成再處理下一個,
就這解決了http1.1應用程序層的http線頭阻塞的問題,http2是否真正解決了線頭阻塞的問題?
之所以前面著重強調的是應用程序層的http線頭阻塞,原因是http2僅在二進制幀處理層拿到完成的tcp數據后解決了線頭阻塞問題,http2同樣基于傳輸層的tcp協議傳輸的,
TCP標頭格式
tcp協議為了保證消息可靠,順序傳遞消息,就有可能造成塞車的情況,所以傳輸層的tcp線頭阻塞問題并沒有得到解決,為了說明這個問題,來模擬下傳輸層tcp傳遞數據包的視角。
當打開b站,我們和瀏覽器都明白我們向b站服務器請求獲取js、css等文件資源,
http2通過前面講的二進制幀數據處理層,將數據剁碎,加入stream id,用數據流的方式傳遞數據,其他的,http2并不關心,更不用說到達傳輸層的tcp協議了,甚至都不知道這是傳輸的是http請求、響應的信息,tcp只知道數據包的最大大小通常是1450字節。
http2跟蹤傳輸的指定字節范圍的數據部分,以便可以按正確順序來還原原始數據,
假如tcp數據包2在傳輸過程中丟失了,
那么tcp數據包1和3會提前達到緩沖區,tcp就會發現數據包1和3之間缺少了數據包2的字節范圍數據,哪怕http2知道tcp數據包1中有stream id為1的數據幀,加上tcp數據包3中stream為1的數據幀,可以組裝成一個完整的消息,也要耐心等待tcp至少重返服務器一次,來重傳丟包的數據2的數據副本到達才能繼續解包其他tcp數據交給應用程序層,就是丟失的tcp數據包2阻塞了tcp數據包3導致了tcp的線頭阻塞,所以結論就是http2并沒有完全解決線頭阻塞的問題。
接下來講個http2重要的擴展功能:
http2服務器推送功能
首先正是因為http2具有雙向流、多路復用的特點 ,才更有利于http2提供的服務器推送功能,比如直接打開b站 請求的是首頁,
b站服務器可以將未經過請求的、首頁所需的js、css等文件資源準備好之后直接推送給我們;
同時對于前端開發者,http2更加有益于html5套件的SSE功能(Server-Send Events即服務器發送事件),
服務器推送功能進一步減少了網絡請求的數量,提高了交互的效率。
http2標頭壓縮功能
打開瀏覽器開發者工具,直接進入百度首頁,
百度依然使用的是http1.1版本,
百度首頁的請求標頭是689字節,響應標頭是598字節,點擊標頭標簽可以看到請求和響應的所有標頭信息,嘗試刷新下頁面,看到請求發送標頭大小沒有任何變化,說明http1.1來回總是在說車轱轆話。
b站使用的是http2協議,
b站首次請求的標頭大小是387字節,響應的標頭大小是417字節,進入標頭標簽可以詳細看到http2請求和響應標頭信息,
再次刷新頁面,發現請求標頭縮小至116kb,響應字節也小了,
再次刷新頁面,發現b站請求和響應標頭更小了,而達到這種節約網絡請求的標頭大小的效果就要歸功于http2是使用HPACK來編解碼http標頭的。
HPACK的原理就是建立連接的客戶端和服務器都維護著相同的61個條目的只讀的靜態表和可動態添加條目的空白的動態表,
當請求發送的時候先查找表中的名值,名值完全匹配直接提取該標頭在表中對應的整數索引用于表示該名值對。
如果只匹配名稱,則使用索引表示名稱,值就用霍夫曼huffman算法進行編碼后表示,
表中沒有的,則按照順序添加到動態表中,再使用霍夫曼算法編碼名值對字符后傳遞給服務器;服務器端則反之, 使用表中的索引解碼或者按順序使用霍夫曼算法進行解碼后添加到服務器動態表中,這樣保證雙方的表中保存的數據和整數索引是一致的,在之后的所有請求的標頭會越來越小,減少了網絡數據流量來提高交互的效率。
這是http2標準中已經定義的61個標頭的靜態表,
類似:path這樣的標頭名稱,被稱為偽標頭,出現偽標頭的原因是為了兼容之前的版本,
http2的標頭都是名值對,之前版本的標頭都存在標頭行,http2將http1的標頭行拆分成名值后的名稱前加上了冒號,方便與http2的標頭名稱進行區分。
什么是霍夫曼算法
最優前綴碼算法,利用二叉樹將出現頻率大的符號采用較短的有效編碼,頻率小的有效編碼更長,解決等長編碼解壓時出現歧義的問題,
字節下的數字代表這個字母出現的頻率,
霍夫曼算法會先從出現頻率低的兩個字母,按照左樹代表0放出現頻率小的字母
右樹代表1放出現頻率大的字母的原則合并組成一個新節點。
以此類推最終組成一個二叉樹,
以上6個字母的最終編碼如圖所示,a=0直到f=1100,你是否認為http2每次都對未出現在表中的標頭字符都這樣算一遍?
http2標準按照標頭字符的使用頻率,專門準備了一張霍夫曼編碼表,剛好256個條目,
對應ASCII編碼表示的256個可能的字符,那么對應霍夫曼編碼表,編碼就不需要二叉樹運算了,提高了編碼的效率,例如在霍夫曼編碼表中,位于索引47位置的符號/,由6位二進制編碼011000組成,16進制編碼值就是0x18。
http2留給我們的三個思考
因為客戶端對同一個網站交互越多,
動態表中積累的http標頭條目就越多,標頭壓縮的效果就越好,所以這里有一個http2的優化技巧,交互最好是同域名或者同域名下不同二級域名指向的服務器ip是一樣的,
而且使用的是同一個加密證書,這樣會確保使用相同的靜動態標頭表,這也解釋了為啥泛域名的SSL加密證書每年費用那么貴的原因。
HPACK是按順序編解碼標頭的是不是也會造成線頭阻塞問題?
是的,因為http2數據流可以是無序的,
請求數據流達到服務器后,請求攜帶編碼后的標頭中,
發現有個請求標頭索引比服務器解碼動態標頭表中的最大索引值還大,就會造成無法解碼,而導致使用該請求標頭相關的數據流都會被阻塞,直到攜帶該索引表示的正確霍夫曼編碼的數據流達到后,
才能解碼之前的請求數據流,如果這條數據流丟包了,就會造成塞車時間更長,目前這個塞車問題只能留給http3解決了。
第三個思考是使用多路復用,它真的就比之前版本的按順序傳輸更快嗎
真正解決了交互事務中的線頭阻塞問題了嗎? 答案是不一定
不是說好了速度更快且沒有http線頭阻塞問題?
首先表象的看http2和現代瀏覽器強制了必須使用https加密超文本協議傳輸,那https是如何工作的?
相比于http1.1之前不強制使用https加密,http2在加密交換密鑰的過程中,最少增加了2個往返時間即2RRT(Round-Trip Time),反而減慢了交互速度。
另一個是多路復用中數據被切碎成數據幀,并用stream id標識打包成數據流傳輸,這樣就可以亂序傳輸了。
假如我們一次請求一個js或css文件,根據文件大小假如被切分成了5份和3分,來對比下順序傳輸和亂序傳輸
,順序傳輸的js文件的接收速度比亂序的js傳輸速度更快,
總的到達時間基本一致的,所以在http2中的多路復用,就需要根據實際情況考慮使用優先級控制優化了。
再比如亂序中某個數據的丟包,在緩存中排序組裝消息的時候發現少了一幀,
就需要至少往返一次服務器,拿丟失的數據幀副本,雖然沒阻塞其他的消息,但確實阻塞了自己,所以這個問題 不單單存在于http2,http3的多路復用也存在這個問題,雖然這種數據流的線頭阻塞是概率問題,但確實存在。
它是基于QUIC協議的升級版本,
已經知道了http2基于傳輸層的tcp協議傳輸,僅僅解決了在應用程序層的http線頭阻塞的問題,
但又由于傳輸層的協議基本由系統實現,應用程序層無法干涉,所以http3就想,既然動不了你,干嘛不直接拋棄你,http3就直接使用了傳輸效率更高的UDP,雖然UDP不保證消息傳輸的可靠性,但在http2已經解決了應用程序層http的線頭阻塞問題的基礎之上,
http3在應用程序層適配UDP并保證數據傳輸的可靠性,就推出了基于UDP的QUIC協議,可以說它是tcp2.0,只不過是在應用程序層實現的,QUIC協議實現了tcp的可靠性、擁塞控制、流量控制、排序等功能和保留了http2基于流的多路復用及http2優化的其他功能。
http3將基于UDP的流作為一等公民和http2的流處理有2個重要區別
一個區別是http3使用動態標頭編碼QPACK和http2使用HPACK,QPACK和HPACK在編解碼方式都是一樣的,重要區別在與QPACK增加了靜態表中的標頭條目至98個。
另一個就是解決HPACK按順序解碼容易造成線頭阻塞的問題,
QPACK的解決辦法就是使用指令獨立流,
提前講好需要增加的標頭項,使用添加指令發送給解碼方,解碼方收到后回復確認標頭指令,從而雙方利用標頭動態表中標頭項的狀態,來確認標頭是否完成同步,標頭項狀態未被確認的,請求的數據流就不會使用該項標頭的索引編碼標頭以解決動態標頭解碼的塞車問題。
另一個區別就是因為QUIC協議實現了tcp協議的所有功能,那QUIC協議如何規避tcp線頭阻塞的缺點?
http2存在tcp的線頭阻塞,是因為tcp只知道跟蹤攜帶的字節范圍數據,并不知道我們前面講的tcp線頭阻塞例子中數據包1中的stream id為1的數據幀和數據包3中的stream id為1的數據幀可以組裝成一個消息,因為tcp根本不知道傳輸的具體細節。
在http3中QUIC協議優化了幀的組成部分,脫掉了tcp外衣,直接在數據幀中加入了Offset字節范圍幀頭,
這樣就能根據stream id和Offset幀頭,判斷哪些數據包中的幀數是同一個消息,因此例子中數據包1中的stream id為1的數據幀,
就可以和數據包3中的steram id為1的數據幀組成一個消息,因為它們的字節范圍可以匹配的上,這樣就解決了即使丟了數據包2,也不影響其他消息的情況,從而消除了tcp線頭阻塞的缺點。
http3的三個重要優點
基于UDP有個明顯的好處就是數據到達傳輸層是需要加入傳輸層協議的頭部。
http2和之前的版本使用的是tcp協議,
需要20-60個字節的頭部,
而http3采用傳輸層的UDP協議,頭部僅僅8個字節,這樣就進一步優化了交互數據的大小,提高了傳輸速度,QUIC協議基于UDP有個更大的好處就是0往返時間建立連接即0 RTT,
tcp建立連接是需要經歷3次握手和四次揮手的,QUIC協議是基于UDP的,就不需要三次握手和四次揮手,名義上實現了0 RTT,但是http3也需要加密傳輸,直接將加密協商信息和請求數據一起發送給服務器,只需要1個RTT就可以完成建立連接,相比于http2的加密協商至少需要2個RTT來說,還少了一個RTT。
第三個優點:tcp是基于連接的,而QUIC協議基于UDP的數據傳輸,是基于報文的,那http3怎么識別當前連接并實現多路復用的?
使用tcp建立的連接會基于源IP、源端口、目的IP和目的端口四元組信息,來確認是否已經建立過該連接,假如說從wifi切換到5G至少會導致四元組信息中的源IP地址信息會發生改變,tcp就必須重新建立連接,
而QUIC協議使用的是隨機數作為connection id即連接id,來識別客戶端和同域名服務器建立連接,從而不受網絡變化的影響,通過connection id確定了連接,當然就可以實現和http2一樣的雙向流多路復用的功能,基于這個優點,http3協議就非常適合移動互聯網了。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。