1. 程式人生 > 其它 >HTTP 2.0面試通關:強制快取和協商快取

HTTP 2.0面試通關:強制快取和協商快取

大家好,我是Jerry哥,在位元組跳動實習期間官方文件一直強調使用http2.0,那麼我們今天來看看裡頭的一些玄機。

目錄

超文字傳輸協議(HyperText Transfer Protocol,HTTP)是目前使用最廣泛的應用層協議。 在網站、App、開放介面中都可以看到它。HTTP 協議設計非常簡單,但是涵蓋的內容很多。相信你平時工作中已經多多少少接觸過這個協議,這一講我們會挑選其中一部分重點介紹,比如高頻面試內容,以及容易產生理解誤區的內容,幫助你深入學習 HTTP 協議。

HTTP1.0和HTTP1.1的區別

長連線(Persistent Connection)

HTTP1.1支援長連線和請求的流水線處理,在一個TCP連線上可以傳送多個HTTP請求和響應,減少了建立和關閉連線的消耗和延遲,在HTTP1.1中預設開啟長連線keep-alive,一定程度上彌補了HTTP1.0每次請求都要建立連線的缺點。HTTP1.0需要使用keep-alive引數來告知伺服器端要建立一個長連線。

節約頻寬

HTTP1.0中存在一些浪費頻寬的現象,例如客戶端只是需要某個物件的一部分,而伺服器卻將整個物件送過來了,並且不支援斷點續傳功能。HTTP1.1支援只發送header資訊(不帶任何body資訊),如果伺服器認為客戶端有許可權請求伺服器,則返回100,客戶端接收到100才開始把請求body傳送到伺服器;如果返回401,客戶端就可以不用傳送請求body了節約了頻寬。

HOST域

在HTTP1.0中認為每臺伺服器都繫結一個唯一的IP地址,因此,請求訊息中的URL並沒有傳遞主機名(hostname),HTTP1.0沒有host域。隨著虛擬主機技術的發展,在一臺物理伺服器上可以存在多個虛擬主機(Multi-homed Web Servers),並且它們共享一個IP地址。HTTP1.1的請求訊息和響應訊息都支援host域,且請求訊息中如果沒有host域會報告一個錯誤(400 Bad Request)。

快取處理

在HTTP1.0中主要使用header裡的If-Modified-Since,Expires來做為快取判斷的標準,HTTP1.1則引入了更多的快取控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供選擇的快取頭來控制快取策略。

請求響應和長連線

HTTP 協議採用請求/返回模型。客戶端(通常是瀏覽器)發起 HTTP 請求,然後 Web 服務端收到請求後將資料回傳。

HTTP 的請求和響應都是文字,你可以簡單認為 HTTP 協議利用 TCP 協議傳輸文字。當用戶想要看一張網頁的時候,就傳送一個文字請求到 Web 伺服器,Web 伺服器解析了這段文字,然後給瀏覽器將網頁回傳。

那麼這裡有一個問題,是不是每次傳送一個請求,都建立一個 TCP 連線呢?

當然不能這樣,為了節省握手、揮手的時間。當瀏覽器傳送一個請求到 Web 伺服器的時候,Web 伺服器內部就設定一個定時器。在一定範圍的時間內,如果客戶端繼續傳送請求,那麼伺服器就會重置定時器。如果在一定範圍的時間內,伺服器沒有收到請求,就會將連線斷開。這樣既防止浪費握手、揮手的資源,同時又避免一個連線佔用時間過長無法回收導致記憶體使用效率下降。

這個能力可以利用 HTTP 協議頭進行配置,比如下面這條請求頭:

Keep-Alive: timeout=10s

會告訴 Web 伺服器連線的持續時間是 10s,如果 10s 內沒有請求,那麼連線就會斷開。

HTTP 2.0 的多路複用

Keep-Alive 並不是伯納斯·李設計 HTTP 協議時就有的能力。伯納斯·李設計的第一版 HTTP 協議是 0.9 版,後來隨著協議逐漸完善,有了 1.0 版。而 Keep-Alive 是 HTTP 1.1 版增加的功能,目的是應對越來越複雜的網頁資源載入。從 HTTP 協議誕生以來,網頁中需要的資源越來越豐富,開啟一張頁面需要傳送的請求越來越多,於是就產生了 Keep-Alive 的設計。

同樣,當一個網站需要載入的資源較多時,瀏覽器會嘗試併發傳送請求(利用多執行緒技術) 。瀏覽器會限制同時傳送併發請求的數量,通常是 6 個,這樣做一方面是對使用者本地體驗的一種保護,防止瀏覽器搶佔太多網路資源;另一方面也是對站點服務的保護,防止瞬時流量過大。

在 HTTP 2.0 之後,增加了多路複用能力。和之前我們講 RPC 框架時提到的多路複用類似,請求、返回會被拆分成切片,然後混合傳輸。這樣請求、返回之間就不會阻塞。你可以思考,對於一個 TCP 連線,在 HTTP 1.1 的 Keep-Alive 設計中,第二個請求,必須等待第一個請求返回。如果第一個請求阻塞了,那麼後續所有的請求都會阻塞。而 HTTP 2.0 的多路複用,將請求返回都切分成小片,這樣利用同一個連線,請求相當於並行的發出,互相之間不會有干擾。

HTTP 方法

在 Restful 架構中,除了約定了上述整體架構方案之外,還約束了一些實現細節,比如用名詞性的介面和 HTTP 方法來設計服務端提供的介面。

我們用 GET 獲取資料,或者進行查詢。比如下面這個例子,就是在獲取 id 為 123 的訂單資料:

GET /order/123

GET 是 HTTP 方法,/order 是一種名詞性質的命名。這樣設計語義非常清晰,這個介面是獲取訂單的資料(也就是訂單的 Representation 用的)。

對於更新資料的場景,按照 HTTP 協議的約定,PUT 是一種冪等的更新行為,POST 是一種非冪等的更新行為。舉個例子:

PUT /order/123 
{...訂單資料}

上面我們用 PUT 更新訂單,如果訂單 123 還沒有建立,那麼這個介面會建立訂單。如果 123 已經存在,那麼這個介面會更新訂單 123 的資料。為什麼是這樣?因為 PUT 代表冪等,對於一個冪等的介面,請求多少遍最終的狀態是一致的,也就是說操作的都是同一筆訂單。

如果換成用 POST 更新訂單:

POST /order
{...訂單資料}

POST 代表非冪等的設計,像上面這種用 POST 提交表單的介面,呼叫多次往往會產生多個訂單。也就是非冪等的設計每次呼叫結束後都會產生新的狀態。

另外在 HTTP 協議中,還約定了 DELETE 方法用於刪除資料。其實還有幾個方法,感興趣的同學可以查詢下,比如 OPTIONS、PATCH,然後我們在留言區中討論。

快取
在 HTTP 的使用中,我們經常會遇到兩種快取,強制快取和協商快取,接下來一一介紹。

強制快取

你的公司用版本號管理某個對外提供的 JS 檔案。比如說 libgo.1.2.3.js,就是 libgo 的 1.2.3 版本。其中 1 是主版本,2 是副版本,3 是補丁編號。每次你們有任何改動,都會更新 libgo 版本號。在這種情況下,當瀏覽器請求了一次 libgo.1.2.3.js 檔案之後,還需要再請求一次嗎?

整理下我們的需求,瀏覽器在第一次進行了GET /libgo.1.2.3.js這個操作後,如果後續某個網頁還用到了這個檔案(libgo.1.2.3.js),我們不再發送第二次請求。這個方案要求瀏覽器將檔案快取到本地,並且設定這個檔案的失效時間(或者永久有效)。這種請求過一次不需要再次傳送請求的快取模式,在 HTTP 協議中稱為強制快取。當一個檔案被強制快取後,下一次請求會直接使用本地版本,而不會真的發出去。

使用強制快取時要注意,千萬別把需要動態更新的資料強制快取。一個負面例子就是小明把獲取使用者資訊資料的介面設定為強制快取,導致使用者更新了自己的資訊後,一直要等到強制快取失效才能看到這次更新。

協商快取

我們再說一個場景:小明開發了一個介面,這個介面提供全國省市區的 3 級資訊。先問你一個問題,這個場景可以用強制快取嗎?小明一開始覺得強制快取可以,然後突然有一天接到運營的通知,某市下屬的兩個縣合併了,需要調整介面資料。小明錯手不急,更新了介面資料,但是資料要等到強制快取失效。

為了應對這種場景,HTTP 協議還設計了協商快取。協商快取啟用後,第一次獲取介面資料,會將資料快取到本地,並存儲下資料的摘要。第二次請求時,瀏覽器檢查到本地有快取,將摘要傳送給服務端。服務端會檢查服務端資料的摘要和瀏覽器傳送來的是否一致。如果不一致,說明服務端資料發生了更新,服務端會回傳全部資料。如果一致,說明資料沒有更新,服務端不需要回傳資料。

從這個角度看,協商快取的方式節省了流量。對於小明開發的這個介面,多數情況下協商快取會生效。當小明更新了資料後,協商快取失效,客戶端資料可以馬上更新。和強制快取相比,協商快取的代價是需要多發一次請求。

總結

目前 HTTP 協議已經發展到了 2.0 版本,不少網站都更新到了 HTTP 2.0。大部分瀏覽器、CDN 也支援了 HTTP 2.0。 HTTP 2.0 更多解決隊頭阻塞、HPack 壓縮演算法、Server Push 等問題,當你將這些回答出來,基本就可以獲得面試官的青睞了。

頭部資料壓縮

在HTTP1.1中,HTTP請求和響應都是由狀態行、請求/響應頭部、訊息主體三部分組成。一般而言,訊息主體都會經過gzip壓縮,或者本身傳輸的就是壓縮過後的二進位制檔案,但狀態行和頭部卻沒有經過任何壓縮,直接以純文字傳輸。隨著Web功能越來越複雜,每個頁面產生的請求數也越來越多,導致消耗在頭部的流量越來越多,尤其是每次都要傳輸UserAgent、Cookie這類不會頻繁變動的內容,完全是一種浪費。

HTTP1.1不支援header資料的壓縮,HTTP2.0使用HPACK演算法對header的資料進行壓縮,這樣資料體積小了,在網路上傳輸就會更快。

伺服器推送

服務端推送是一種在客戶端請求之前傳送資料的機制。網頁使用了許多資源:HTML、樣式表、指令碼、圖片等等。在HTTP1.1中這些資源每一個都必須明確地請求。這是一個很慢的過程。瀏覽器從獲取HTML開始,然後在它解析和評估頁面的時候,增量地獲取更多的資源。因為伺服器必須等待瀏覽器做每一個請求,網路經常是空閒的和未充分使用的。

為了改善延遲,HTTP2.0引入了server push,它允許服務端推送資源給瀏覽器,在瀏覽器明確地請求之前,免得客戶端再次建立連線傳送請求到伺服器端獲取。這樣客戶端可以直接從本地載入這些資源,不用再通過網路。

更多問題歡迎關注私聊~