如果你喜歡我的文章,希望點贊👍 收藏 📁 評論 💬 三連支持一下,謝謝你,這對我真的很重要!
前兩天在「即刻」上一時興起寫了一段 HTTP 優先級的發展歷程,這兩天覺得當時寫的還是太倉促了,所以準備寫個 Blog 擴寫一下,就「優先級」這個概念縱向分析,看看這些基礎協議的發展和進化。
正式講之前先列個時間線,這樣的話對以下內容的理解會有個更直觀的認識:
- 1997 年,經過前幾年幾個版本的迭代,HTTP/1.1 最終定稿。伴隨着瀏覽器的發展,互聯網時代拉開帷幕
- 2015 年,HTTP/2 發佈,主打解決性能問題,新增了一堆特性。到現在為止,全球 35.5% 的網站使用了 HTTP/2
- 2022 年,HTTP/3 發佈,最大的特點是傳輸層替換成 UDP,到現在為止,全球 27% 的網站使用了 HTTP/3
HTTP 歷史發展推薦閲讀 MDN 的文章:《Evolution of HTTP》
雖然 HTTP 已經發展了 30 多年,從一個簡單的文本協議已經進化到加密二進制流,但是得益於良好的協議設計和各種調試工具的發展,對於大部分應用程序開發者來説,底層到底是 1.1 還是 3,在使用上基本都是無感的。但是對於一些特殊需求(例如性能優化),就必須撥開這些封裝,窺探一下這些字節中的細節了。
下面我們就來談談 HTTP 中 「優先級(prioritization)」是如何設計的。
HTTP/1.1: 啥都沒有
對於 HTTP/1.1老夥計來説,根本就沒有優先級的概念。
如果沒有開啓 keep-alive,那就是一個 TCP 上傳輸一個來回的 HTTP,都是一對一的關係,何來多請求場景下的優先級概念呢?
如果開啓了 keep-alive,策略也非常簡單,那就是 FIFO(First In First Out),一個非常典型的隊列場景,所以也不存在優先級問題,誰來得早就發送誰,主打一個公平。
當然,這種絕對的公平引起了一個問題,那就是隊頭阻塞(Head-of-Line blocking),簡單説就是如果前面的請求卡住了,後面的請求就只能乾瞪眼等着空耗,HTTP/2 和 HTTP/3 都試着解決這個問題(但完美的解決方案是不存在的)。由於這部分內容太多了,我拆到另一篇 Blog 再細講。
其實 HTTP/1.1 還有個 pipelining 方案,沒有解決 HOL 問題的同時還有嚴重的性能問題,所以主流瀏覽器和服務器都沒支持,就是個廢案,但可以當個擴展閲讀考考古
HTTP/2: 過度設計
HTTP/2 的一個宣傳點是多路複用(Multiplexing),也就是説一個 TCP 鏈接上併發跑多個 HTTP 請求。這種一對多場景,就有了調度策略和優先級的需求。
我們先不説 HTTP/2 是如何設計多請求下的優先級方案的,我們先做個思想實驗,嘗試自己從 0 設計這個優先級方案。
假設 A,B,C 三個大小一樣的圖片,我們要用 HTTP/2 傳輸,那麼拍腦門用最快的思路想一下,可以怎麼做?
第一種思路,就是先傳輸 A,再傳輸 B,最後傳輸 C。但這和 HTTP/1.1 也太像了,沒有新意,用户不會買單的。
第二種思路,突出併發流式概念,我先傳輸 A 一些,再傳輸 B 一些,再傳輸 C 一些,主打一個風水輪流轉(Round Robin 策略)。再加上終端流式加載渲染,用户體感渲染速度直接快了一半。
這時候我們再上上難度,比如説這個 A 是文章首圖,B 和 C 是文章內的例圖,那麼對於性能指標和用户體驗來説,A 快些加載肯定是更合適的,那麼就要提高 A 的加載優先級,HTTP/2 這一個連接裏,應該分配給 A 更多的帶寬。
在現實場景裏,一個頁面有幾十個資源請求,大小,類型,調用時機都是不一樣的,非常的複雜。所以 HTTP/2 為了適配這種複雜度,就設計了一個更復雜的優先級調度策略 😅。
由於存在調用順序等問題,HTTP/2 先搞了個優先級依賴樹(dependency tree),根據資源之間的調度關係和構建一顆依賴樹。例如一個 html 頁面,裏面加載了 css 和 js,還有一張 jpg。css 裏面還加載了一張 png。那麼根據這個依賴關係可以構建出這樣一顆樹:
這顆樹展示了資源的依賴關係,一般來説,終端應該先加載根節點資源,加載完後再加載子節點資源。
但對於樹這種結構來説,還存在一個問題,那就是同級的子節點之間按道理來説也應該有優先級順序。
比如説一個 html(根節點)發起了 3 個圖片(子節點)請求,其中 1 是首圖,2 是首屏圖片,3 是屏幕外的圖片。這 3 張圖雖然都是圖片請求,互相也沒有從屬關係,但是從用户體驗來説,優先級應該這樣最合理:
1.ipg > 2.jpg >> 3.jpg
所以為了應對這種場景,HTTP/2 大手一揮,又給每個節點劃分了 256 個權重分級(weight),足夠你慢慢分了。
當然,除了這些可以初步分析就能確定的優先級,HTTP/2 還支持動態修改優先級,後來者可以向原先構建的優先級樹上隨時掛節點,已經處在隊列裏的可以隨時插隊。
最後,為了緊急情況,還提供了獨佔式標誌位(exclusive flag),設為 1 就可以不顧規則,全佔帶寬,突出一個霸道。
到此,你已經可以看出 HTTP/2 的優先級有多複雜了:依賴樹 x 256 個優先級 x 獨佔位 x 動態調整,互相乘起來複雜度飆升。
站在現在這個時間點看,毫無疑問,HTTP/2 的優先級策略,就是過度設計了。沒有幾個開發者可以 cover 這個爆表的複雜度。三大主流瀏覽器的調度策略,只用了 HTTP/2 很小的一部分內容(這個內容也夠單獨寫一篇文章了);服務端上,完全支持 HTTP/2 複雜度調度策略的服務器也沒幾個。
雖然優先級調度策略支持不太行,各端只支持了一小部分能力,但是 HTTP/2 還有很多其它的優化,綜合來看,在統計數據上 HTTP/2 的性能還是強於 HTTP/1.1 的,還是非常值得升級的。
HTTP/3: 先跑幾年
HTTP/2 已經落入規範並實施多年了,木已成舟,想改也改不動了,隔壁 HTTP/3 也開始寫 RFC 準備動工了。
先不説 HTTP/3 的那些特性,HTTP/2 優先級設計成這個德行,設計委員會在座的各位都有有責任的。如果把上個領導班子的策略繼續貫徹下去,那瀏覽器和服務器肯定都是沒辦法落實的,最後受苦的還是廣大羣眾,這樣子會出問題的。所以 HTTP/3 吸取了教訓,對優先級進行了大刀闊斧的改革,刪繁就簡,寫成了 RFC 9218,八十老翁都能看懂。
- 首先 HTTP/3 刪掉了依賴樹的概念,優先級也從原來的 256 個減到 8 個,名為 urgency
- 請求獨佔的特性也去了,又減少了一個複雜度
- 動態修改優先級的能力還是得以保留,這個算新版 HTTP 的特性,而且出於靈活度也是必須保留的
- 新增了一個開關,表示是否需要增量傳輸,也就是説是否可以和其它資源交錯(interleaved)傳輸
那麼對於一個請求來説,啥都不做,開局他新手屬性是 3,不流式傳輸。對於 HTTP/3 的整體傳輸來説,流程是這樣的:
- 先傳輸標誌了 P0 級的資源,全傳輸完了再傳輸 P1 級的,直到全部傳完
-
同一級的資源,如果都是非增量傳輸的,那就按先來後到的規則,誰先來的誰先走(FIFO)
- 如果存在增量傳播的資源,那就交錯傳輸
- 如果同級資源同時存在增量和非增量的資源,RFC 從理論上沒有做出指導,實際上瀏覽器也不會這樣創建(不要沒事找事)
這樣看的話,HTTP/3 的優先級策略還是很清晰易懂的,沒 HTTP/2 那麼可怕,又沒有 HTTP/1.1 那麼簡陋。那它的調度策略可以完美的均衡設計複雜度和現實複雜度嗎?
答案是,大家也不知道。畢竟 HTTP/3 是 2022 年年底才正式發佈的協議,線上也就跑了一年多,誰心裏也沒譜。到底是不是解決了歷史問題,還是又引入新的難題,到底是「遙遙領先」,還是「又不是不能用」,都需要時間去驗證。
結語
到此 HTTP 三個大版本的「優先級」發展歷程我就梳理完了,大家也可以看出,實際上對於這些協議的頂層設計者來説,基本上從項目運行的一開始就偏離了自己的設計路線的,我想類似的事情只要大家工作幾年都會有所感觸。
這篇文章我主要是從協議的角度去講解優先級的,那麼下一篇我們就來嘮嘮,瀏覽器是如何配合 HTTP 協議中的優先級的。
如果你喜歡我的文章,希望點贊👍 收藏 📁 評論 💬 三連支持一下,謝謝你,這對我真的很重要!
歡迎大家關注我的微信公眾號:鹵代烴實驗室,目前專注前端技術,對圖形學也有一些微小研究。
原文鏈接 👉 [🪢 [網絡協議] 淺談 HTTP 優先級算法的演進](https://supercodepower.com/http-prioritization),更新更及時,閲讀體驗更佳
參考鏈接
- RFC 9113 HTTP/2
- RFC 9114 HTTP/3
- RFC 9218 Extensible Prioritization Scheme for HTTP
- HTTP/2 Prioritization
- Better HTTP/2 Prioritization for a Faster Web
- HTTP/3 Prioritization Demystified
- Introducing HTTP/3 Prioritization
- HTTP/2 IN ACTION