問你一句:「你知道 HTTP/1.1 該如何優化嗎?」
我想你第一時間想到的是,使用 KeepAlive 將 HTTP/1.1 從短連線改成長連結。
這個確實是一個優化的手段,它是從底層的傳輸層這一方向入手的,通過減少 TCP 連線建立和斷開的次數,來減少了網路傳輸的延遲,從而提高 HTTP/1.1 協議的傳輸效率。
但其實還可以從其他方向來優化 HTTP/1.1 協議,比如有如下 3 種優化思路:
- 儘量避免傳送 HTTP 請求;
- 在需要傳送 HTTP 請求時,考慮如何減少請求次數;
- 減少伺服器的 HTTP 響應的資料大小;
下面,就針對這三種思路具體看看有哪些優化方法。
1 如何避免傳送 HTTP 請求?
這個思路你看到是不是覺得很奇怪,不傳送 HTTP 請求,那還客戶端還怎麼和伺服器互動資料?小林你這不是耍流氓嘛?
冷靜冷靜,你說的沒錯,客戶端當然要向伺服器傳送請求的。
但是,對於一些具有重複性的 HTTP 請求,比如每次請求得到的資料都一樣的,我們可以把這對「請求-響應」的資料都快取在本地,那麼下次就直接讀取本地的資料,不必在通過網路獲取伺服器的響應了,這樣的話 HTTP/1.1 的效能肯定肉眼可見的提升。
所以,避免傳送 HTTP 請求的方法就是通過快取技術,HTTP 設計者早在之前就考慮到了這點,因此 HTTP 協議的頭部有不少是針對快取的欄位。
那快取是如何做到的呢?
客戶端會把第一次請求以及響應的資料儲存在本地磁碟上,其中將請求的 URL 作為 key,而響應作為 value,兩者形成對映關係。
這樣當後續發起相同的請求時,就可以先在本地磁碟上通過 key 查到對應的 value,也就是響應,如果找到了,就直接從本地讀取該響應。毋庸置疑,讀取本次磁碟的速度肯定比網路請求快得多,如下圖:
聰明的你可能想到了,萬一快取的響應不是最新的,而客戶端並不知情,那麼該怎麼辦呢?
放心,這個問題 HTTP 設計者早已考慮到。
所以,伺服器在傳送 HTTP 響應時,會估算一個過期的時間,並把這個資訊放到響應頭部中,這樣客戶端在檢視響應頭部的資訊時,一旦發現快取的響應是過期的,則就會重新傳送網路請求。HTTP 關於緩說明會的頭部欄位很多,這部分內容留在下次文章,這次暫時不具體說明。
如果客戶端從第一次請求得到的響應頭部中發現該響應過期了,客戶端重新傳送請求,假設伺服器上的資源並沒有變更,還是老樣子,那麼你覺得還要在伺服器的響應帶上這個資源嗎?
很顯然不帶的話,可以提高 HTTP 協議的效能,那具體如何做到呢?
只需要客戶端在重新傳送請求時,在請求的 Etag
頭部帶上第一次請求的響應頭部中的摘要,這個摘要是唯一標識響應的資源,當伺服器收到請求後,會將本地資源的摘要與請求中的摘要做個比較。
如果不同,那麼說明客戶端的快取已經沒有價值,伺服器在響應中帶上最新的資源。
如果相同,說明客戶端的快取還是可以繼續使用的,那麼伺服器僅返回不含有包體的 304 Not Modified
響應,告訴客戶端仍然有效,這樣就可以減少響應資源在網路中傳輸的延時,如下圖:
快取真的是效能優化的一把萬能鑰匙,小到 CPU Cache、Page Cache、Redis Cache,大到 HTTP 協議的快取。
2 如何減少 HTTP 請求次數?
減少 HTTP 請求次數自然也就提升了 HTTP 效能,可以從這 3 個方面入手:
- 減少重定向請求次數;
- 合併請求;
- 延遲傳送請求;
2.1 減少重定向請求次數
我們先來看看什麼是重定向請求?
伺服器上的一個資源可能由於遷移、維護等原因從 url1 移至 url2 後,而客戶端不知情,它還是繼續請求 url1,這時伺服器不能粗暴地返回錯誤,而是通過 302
響應碼和 Location
頭部,告訴客戶端該資源已經遷移至 url2 了,於是客戶端需要再傳送 url2 請求以獲得伺服器的資源。
那麼,如果重定向請求越多,那麼客戶端就要多次發起 HTTP 請求,每一次的 HTTP 請求都得經過網路,這無疑會越降低網路效能。
另外,服務端這一方往往不只有一臺伺服器,比如源伺服器上一級是代理伺服器,然後代理伺服器才與客戶端通訊,這時客戶端重定向就會導致客戶端與代理伺服器之間需要 2 次訊息傳遞,如下圖:
如果重定向的工作交由代理伺服器完成,就能減少 HTTP 請求次數了,如下圖:
而且當代理伺服器知曉了重定向規則後,可以進一步減少訊息傳遞次數,如下圖:
除了 302
重定向響應碼,還有其他一些重定向的響應碼,你可以從下圖看到:
其中,301
和 308
響應碼是告訴客戶端可以將重定向響應快取到本地磁碟,之後客戶端就自動用 url2 替代 url1 訪問伺服器的資源。
2.2 合併請求
如果把多個訪問小檔案的請求合併成一個大的請求,雖然傳輸的總資源還是一樣,但是減少請求,也就意味著減少了重複傳送的 HTTP 頭部。
另外由於 HTTP/1.1 是請求響應模型,如果第一個傳送的請求,未收到對應的響應,那麼後續的請求就不會傳送,於是為了防止單個請求的阻塞,所以一般瀏覽器會同時發起 5-6 個請求,每一個請求都是不同的 TCP 連線,那麼如果合併了請求,也就會減少 TCP 連線的數量,因而省去了 TCP 握手和慢啟動過程耗費的時間。
接下來,具體看看合併請求的幾種方式。
有的網頁會含有很多小圖片、小圖示,有多少個小圖片,客戶端就要發起多少次請求。那麼對於這些小圖片,我們可以考慮使用 CSS Image Sprites
技術把它們合成一個大圖片,這樣瀏覽器就可以用一次請求獲得一個大圖片,然後再根據 CSS 資料把大圖片切割成多張小圖片。
這種方式就是通過將多個小圖片合併成一個大圖片來減少 HTTP 請求的次數,以減少 HTTP 請求的次數,從而減少網路的開銷。
除了將小圖片合併成大圖片的方式,還有服務端使用 webpack
等打包工具將 js、css 等資源合併打包成大檔案,也是能達到類似的效果。
另外,還可以將圖片的二進位制資料用 base64
編碼後,以 URL 的形式潛入到 HTML 檔案,跟隨 HTML 檔案一併傳送.
<image src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPoAAAFKCAIAAAC7M9WrAAAACXBIWXMAA ... />
這樣客戶端收到 HTML 後,就可以直接解碼出資料,然後直接顯示圖片,就不用再發起圖片相關的請求,這樣便減少了請求的次數。
圖來源於:陳健平的CSDN可以看到,合併請求的方式就是合併資源,以一個大資源的請求替換多個小資源的請求。
但是這樣的合併請求會帶來新的問題,當大資源中的某一個小資源發生變化後,客戶端必須重新下載整個完整的大資原始檔,這顯然帶來了額外的網路消耗。
2.3 延遲傳送請求
不要一口氣吃成大胖子,一般 HTML 裡會含有很多 HTTP 的 URL,當前不需要的資源,我們沒必要也獲取過來,於是可以通過「按需獲取」的方式,來減少第一時間的 HTTP 請求次數。
請求網頁的時候,沒必要把全部資源都獲取到,而是隻獲取當前使用者所看到的頁面資源,當使用者向下滑動頁面的時候,再向伺服器獲取接下來的資源,這樣就達到了延遲傳送請求的效果。
3 如何減少 HTTP 響應的資料大小?
對於 HTTP 的請求和響應,通常 HTTP 的響應的資料大小會比較大,也就是伺服器返回的資源會比較大。
於是,我們可以考慮對響應的資源進行壓縮,這樣就可以減少響應的資料大小,從而提高網路傳輸的效率。
壓縮的方式一般分為 2 種,分別是:
- 無失真壓縮;
- 有失真壓縮;
3.1 無失真壓縮
無失真壓縮是指資源經過壓縮後,資訊不被破壞,還能完全恢復到壓縮前的原樣,適合用在文字檔案、程式可執行檔案、程式原始碼。
首先,我們針對程式碼的語法規則進行壓縮,因為通常程式碼檔案都有很多換行符或者空格,這些是為了幫助程式設計師更好的閱讀,但是機器執行時並不要這些符,把這些多餘的符號給去除掉。
接下來,就是無失真壓縮了,需要對原始資源建立統計模型,利用這個統計模型,將常出現的資料用較短的二進位制位元序列表示,將不常出現的資料用較長的二進位制位元序列表示,生成二進位制位元序列一般是「霍夫曼編碼」演算法。
gzip 就是比較常見的無失真壓縮。客戶端支援的壓縮演算法,會在 HTTP 請求中通過頭部中的 Accept-Encoding
欄位告訴伺服器:
Accept-Encoding: gzip, deflate, br
伺服器收到後,會從中選擇一個伺服器支援的或者合適的壓縮演算法,然後使用此壓縮演算法對響應資源進行壓縮,最後通過響應頭部中的 content-encoding
欄位告訴客戶端該資源使用的壓縮演算法。
content-encoding: gzip
gzip 的壓縮效率相比 Google 推出的 Brotli 演算法還是差點意思,也就是上文中的 br,所以如果可以,伺服器應該選擇壓縮效率更高的 br 壓縮演算法。
3.2 有失真壓縮
與無失真壓縮相對的就是有失真壓縮,經過此方法壓縮,解壓的資料會與原始資料不同但是非常接近。
有失真壓縮主要將次要的資料捨棄,犧牲一些質量來減少資料量、提高壓縮比,這種方法經常用於壓縮多媒體資料,比如音訊、視訊、圖片。
可以通過 HTTP 請求頭部中的 Accept
欄位裡的「 q 質量因子」,告訴伺服器期望的資源質量。
Accept: audio/*; q=0.2, audio/basic
關於圖片的壓縮,目前壓縮比較高的是 Google 推出的 WebP 格式,它與常見的 Png 格式圖片的壓縮比例對比如下圖:
來源於:https://isparta.github.io/compare-webp/index.html可以發現,相同圖片質量下,WebP 格式的圖片大小都比 Png 格式的圖片小,所以對於大量圖片的網站,可以考慮使用 WebP 格式的圖片,這將大幅度提升網路傳輸的效能。
關於音視訊的壓縮,音視訊主要是動態的,每個幀都有時序的關係,通常時間連續的幀之間的變化是很小的。
比如,一個在看書的視訊,畫面通常只有人物的手和書桌上的書是會有變化的,而其他地方通常都是靜態的,於是只需要在一個靜態的關鍵幀,使用增量資料來表達後續的幀,這樣便減少了很多資料,提高了網路傳輸的效能。對於視訊常見的編碼格式有 H264、H265 等,音訊常見的編碼格式有 AAC、AC3。
總結
這次主要從 3 個方面介紹了優化 HTTP/1.1 協議的思路。
第一個思路是,通過快取技術來避免傳送 HTTP 請求。客戶端收到第一個請求的響應後,可以將其快取在本地磁碟,下次請求的時候,如果快取沒過期,就直接讀取本地快取的響應資料。如果快取過期,客戶端傳送請求的時候帶上響應資料的摘要,伺服器比對後發現資源沒有變化,就發出不帶包體的 304 響應,告訴客戶端快取的響應仍然有效。
第二個思路是,減少 HTTP 請求的次數,有以下的方法:
- 將原本由客戶端處理的重定向請求,交給代理伺服器處理,這樣可以減少重定向請求的次數;
- 將多個小資源合併成一個大資源再傳輸,能夠減少 HTTP 請求次數以及 頭部的重複傳輸,再來減少 TCP 連線數量,進而省去 TCP 握手和慢啟動的網路消耗;
- 按需訪問資源,只訪問當前使用者看得到/用得到的資源,當客戶往下滑動,再訪問接下來的資源,以此達到延遲請求,也就減少了同一時間的 HTTP 請求次數。
第三思路是,通過壓縮響應資源,降低傳輸資源的大小,從而提高傳輸效率,所以應當選擇更優秀的壓縮演算法。
不管怎麼優化 HTTP/1.1 協議都是有限的,不然也不會出現 HTTP/2 和 HTTP/3 協議,後續我們再來介紹 HTTP/2 和 HTTP/3 協議。
好了,此次分享到這就結束了,如果這篇文章對你有幫助,歡迎來個三連,你們的支援就是小林的最大動力,我們下次見!
巨人的肩膀
- https://isparta.github.io/compare-webp/index.html
- https://zh.wikipedia.org/wiki/https://en.wikipedia.org/wiki/Lossy_compression
- https://en.wikipedia.org/wiki/Lossless_compression
- https://time.geekbang.org/column/article/242667
- https://www.tutorialrepublic.com/css-tutorial/css-sprites.php
- https://blog.csdn.net/weixin_38055381/article/details/81504716
- https://blog.csdn.net/weixin_44151887/article/details/106278559