HTTP 2.0面試通關(guān):強制緩存和協(xié)商緩存
- HTTP1.0和HTTP1.1的區(qū)別
- 長連接(Persistent Connection)
- 節(jié)約帶寬
- HOST域
- 緩存處理
- 請求響應(yīng)和長連接
- HTTP 2.0 的多路復(fù)用
- HTTP 方法
- 強制緩存
- 協(xié)商緩存
- 總結(jié)
- 頭部數(shù)據(jù)壓縮
- 服務(wù)器推送
超文本傳輸協(xié)議(HyperText Transfer Protocol,HTTP)是目前使用最廣泛的應(yīng)用層協(xié)議。 在網(wǎng)站、App、開放接口中都可以看到它。HTTP 協(xié)議設(shè)計非常簡單,但是涵蓋的內(nèi)容很多。相信你平時工作中已經(jīng)多多少少接觸過這個協(xié)議。
HTTP1.0和HTTP1.1的區(qū)別
長連接(Persistent Connection)
HTTP1.1支持長連接和請求的流水線處理,在一個TCP連接上可以傳送多個HTTP請求和響應(yīng),減少了建立和關(guān)閉連接的消耗和延遲,在HTTP1.1中默認(rèn)開啟長連接keep-alive,一定程度上彌補了HTTP1.0每次請求都要創(chuàng)建連接的缺點。HTTP1.0需要使用keep-alive參數(shù)來告知服務(wù)器端要建立一個長連接。
節(jié)約帶寬
HTTP1.0中存在一些浪費帶寬的現(xiàn)象,例如客戶端只是需要某個對象的一部分,而服務(wù)器卻將整個對象送過來了,并且不支持?jǐn)帱c續(xù)傳功能。HTTP1.1支持只發(fā)送header信息(不帶任何body信息),如果服務(wù)器認(rèn)為客戶端有權(quán)限請求服務(wù)器,則返回100,客戶端接收到100才開始把請求body發(fā)送到服務(wù)器;如果返回401,客戶端就可以不用發(fā)送請求body了節(jié)約了帶寬。
HOST域
在HTTP1.0中認(rèn)為每臺服務(wù)器都綁定一個唯一的IP地址,因此,請求消息中的URL并沒有傳遞主機名(hostname),HTTP1.0沒有host域。隨著虛擬主機技術(shù)的發(fā)展,在一臺物理服務(wù)器上可以存在多個虛擬主機(Multi-homed Web Servers),并且它們共享一個IP地址。HTTP1.1的請求消息和響應(yīng)消息都支持host域,且請求消息中如果沒有host域會報告一個錯誤(400 Bad Request)。
緩存處理
在HTTP1.0中主要使用header里的If-Modified-Since,Expires來做為緩存判斷的標(biāo)準(zhǔn),HTTP1.1則引入了更多的緩存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供選擇的緩存頭來控制緩存策略。
請求響應(yīng)和長連接
HTTP 協(xié)議采用請求/返回模型。客戶端(通常是瀏覽器)發(fā)起 HTTP 請求,然后 Web 服務(wù)端收到請求后將數(shù)據(jù)回傳。
HTTP 的請求和響應(yīng)都是文本,你可以簡單認(rèn)為 HTTP 協(xié)議利用 TCP 協(xié)議傳輸文本。當(dāng)用戶想要看一張網(wǎng)頁的時候,就發(fā)送一個文本請求到 Web 服務(wù)器,Web 服務(wù)器解析了這段文本,然后給瀏覽器將網(wǎng)頁回傳。
那么這里有一個問題,是不是每次發(fā)送一個請求,都建立一個 TCP 連接呢?
當(dāng)然不能這樣,為了節(jié)省握手、揮手的時間。當(dāng)瀏覽器發(fā)送一個請求到 Web 服務(wù)器的時候,Web 服務(wù)器內(nèi)部就設(shè)置一個定時器。在一定范圍的時間內(nèi),如果客戶端繼續(xù)發(fā)送請求,那么服務(wù)器就會重置定時器。如果在一定范圍的時間內(nèi),服務(wù)器沒有收到請求,就會將連接斷開。這樣既防止浪費握手、揮手的資源,同時又避免一個連接占用時間過長無法回收導(dǎo)致內(nèi)存使用效率下降。
這個能力可以利用 HTTP 協(xié)議頭進(jìn)行配置,比如下面這條請求頭:
- Keep-Alive: timeout=10s
會告訴 Web 服務(wù)器連接的持續(xù)時間是 10s,如果 10s 內(nèi)沒有請求,那么連接就會斷開。
HTTP 2.0 的多路復(fù)用
Keep-Alive 并不是伯納斯·李設(shè)計 HTTP 協(xié)議時就有的能力。伯納斯·李設(shè)計的第一版 HTTP 協(xié)議是 0.9 版,后來隨著協(xié)議逐漸完善,有了 1.0 版。而 Keep-Alive 是 HTTP 1.1 版增加的功能,目的是應(yīng)對越來越復(fù)雜的網(wǎng)頁資源加載。從 HTTP 協(xié)議誕生以來,網(wǎng)頁中需要的資源越來越豐富,打開一張頁面需要發(fā)送的請求越來越多,于是就產(chǎn)生了 Keep-Alive 的設(shè)計。
同樣,當(dāng)一個網(wǎng)站需要加載的資源較多時,瀏覽器會嘗試并發(fā)發(fā)送請求(利用多線程技術(shù)) 。瀏覽器會限制同時發(fā)送并發(fā)請求的數(shù)量,通常是 6 個,這樣做一方面是對用戶本地體驗的一種保護(hù),防止瀏覽器搶占太多網(wǎng)絡(luò)資源;另一方面也是對站點服務(wù)的保護(hù),防止瞬時流量過大。
在 HTTP 2.0 之后,增加了多路復(fù)用能力。和 RPC 框架時提到的多路復(fù)用類似,請求、返回會被拆分成切片,然后混合傳輸。這樣請求、返回之間就不會阻塞。你可以思考,對于一個 TCP 連接,在 HTTP 1.1 的 Keep-Alive 設(shè)計中,第二個請求,必須等待第一個請求返回。如果第一個請求阻塞了,那么后續(xù)所有的請求都會阻塞。而 HTTP 2.0 的多路復(fù)用,將請求返回都切分成小片,這樣利用同一個連接,請求相當(dāng)于并行的發(fā)出,互相之間不會有干擾。
HTTP 方法
在 Restful 架構(gòu)中,除了約定了上述整體架構(gòu)方案之外,還約束了一些實現(xiàn)細(xì)節(jié),比如用名詞性的接口和 HTTP 方法來設(shè)計服務(wù)端提供的接口。
我們用 GET 獲取數(shù)據(jù),或者進(jìn)行查詢。比如下面這個例子,就是在獲取 id 為 123 的訂單數(shù)據(jù):
- GET /order/123
GET 是 HTTP 方法,/order 是一種名詞性質(zhì)的命名。這樣設(shè)計語義非常清晰,這個接口是獲取訂單的數(shù)據(jù)(也就是訂單的 Representation 用的)。
對于更新數(shù)據(jù)的場景,按照 HTTP 協(xié)議的約定,PUT 是一種冪等的更新行為,POST 是一種非冪等的更新行為。舉個例子:
- PUT /order/123
- {...訂單數(shù)據(jù)}
上面我們用 PUT 更新訂單,如果訂單 123 還沒有創(chuàng)建,那么這個接口會創(chuàng)建訂單。如果 123 已經(jīng)存在,那么這個接口會更新訂單 123 的數(shù)據(jù)。為什么是這樣?因為 PUT 代表冪等,對于一個冪等的接口,請求多少遍最終的狀態(tài)是一致的,也就是說操作的都是同一筆訂單。
如果換成用 POST 更新訂單:
- POST /order
- {...訂單數(shù)據(jù)}
POST 代表非冪等的設(shè)計,像上面這種用 POST 提交表單的接口,調(diào)用多次往往會產(chǎn)生多個訂單。也就是非冪等的設(shè)計每次調(diào)用結(jié)束后都會產(chǎn)生新的狀態(tài)。
另外在 HTTP 協(xié)議中,還約定了 DELETE 方法用于刪除數(shù)據(jù)。其實還有幾個方法,感興趣的同學(xué)可以查詢下,比如 OPTIONS、PATCH,然后我們在留言區(qū)中討論。
緩存 在 HTTP 的使用中,我們經(jīng)常會遇到兩種緩存,強制緩存和協(xié)商緩存,接下來一一介紹。
強制緩存
你的公司用版本號管理某個對外提供的 JS 文件。比如說 libgo.1.2.3.js,就是 libgo 的 1.2.3 版本。其中 1 是主版本,2 是副版本,3 是補丁編號。每次你們有任何改動,都會更新 libgo 版本號。在這種情況下,當(dāng)瀏覽器請求了一次 libgo.1.2.3.js 文件之后,還需要再請求一次嗎?
整理下我們的需求,瀏覽器在第一次進(jìn)行了GET /libgo.1.2.3.js這個操作后,如果后續(xù)某個網(wǎng)頁還用到了這個文件(libgo.1.2.3.js),我們不再發(fā)送第二次請求。這個方案要求瀏覽器將文件緩存到本地,并且設(shè)置這個文件的失效時間(或者永久有效)。這種請求過一次不需要再次發(fā)送請求的緩存模式,在 HTTP 協(xié)議中稱為強制緩存。當(dāng)一個文件被強制緩存后,下一次請求會直接使用本地版本,而不會真的發(fā)出去。
使用強制緩存時要注意,千萬別把需要動態(tài)更新的數(shù)據(jù)強制緩存。一個負(fù)面例子就是小明把獲取用戶信息數(shù)據(jù)的接口設(shè)置為強制緩存,導(dǎo)致用戶更新了自己的信息后,一直要等到強制緩存失效才能看到這次更新。
協(xié)商緩存
我們再說一個場景:小明開發(fā)了一個接口,這個接口提供全國省市區(qū)的 3 級信息。先問你一個問題,這個場景可以用強制緩存嗎?小明一開始覺得強制緩存可以,然后突然有一天接到運營的通知,某市下屬的兩個縣合并了,需要調(diào)整接口數(shù)據(jù)。小明錯手不急,更新了接口數(shù)據(jù),但是數(shù)據(jù)要等到強制緩存失效。
為了應(yīng)對這種場景,HTTP 協(xié)議還設(shè)計了協(xié)商緩存。協(xié)商緩存啟用后,第一次獲取接口數(shù)據(jù),會將數(shù)據(jù)緩存到本地,并存儲下數(shù)據(jù)的摘要。第二次請求時,瀏覽器檢查到本地有緩存,將摘要發(fā)送給服務(wù)端。服務(wù)端會檢查服務(wù)端數(shù)據(jù)的摘要和瀏覽器發(fā)送來的是否一致。如果不一致,說明服務(wù)端數(shù)據(jù)發(fā)生了更新,服務(wù)端會回傳全部數(shù)據(jù)。如果一致,說明數(shù)據(jù)沒有更新,服務(wù)端不需要回傳數(shù)據(jù)。
從這個角度看,協(xié)商緩存的方式節(jié)省了流量。對于小明開發(fā)的這個接口,多數(shù)情況下協(xié)商緩存會生效。當(dāng)小明更新了數(shù)據(jù)后,協(xié)商緩存失效,客戶端數(shù)據(jù)可以馬上更新。和強制緩存相比,協(xié)商緩存的代價是需要多發(fā)一次請求。
總結(jié)
目前 HTTP 協(xié)議已經(jīng)發(fā)展到了 2.0 版本,不少網(wǎng)站都更新到了 HTTP 2.0。大部分瀏覽器、CDN 也支持了 HTTP 2.0。HTTP 2.0 更多解決隊頭阻塞、HPack 壓縮算法、Server Push 等問題,當(dāng)你將這些回答出來,基本就可以獲得面試官的青睞了。
頭部數(shù)據(jù)壓縮
在HTTP1.1中,HTTP請求和響應(yīng)都是由狀態(tài)行、請求/響應(yīng)頭部、消息主體三部分組成。一般而言,消息主體都會經(jīng)過gzip壓縮,或者本身傳輸?shù)木褪菈嚎s過后的二進(jìn)制文件,但狀態(tài)行和頭部卻沒有經(jīng)過任何壓縮,直接以純文本傳輸。隨著Web功能越來越復(fù)雜,每個頁面產(chǎn)生的請求數(shù)也越來越多,導(dǎo)致消耗在頭部的流量越來越多,尤其是每次都要傳輸U(kuò)serAgent、Cookie這類不會頻繁變動的內(nèi)容,完全是一種浪費。
HTTP1.1不支持header數(shù)據(jù)的壓縮,HTTP2.0使用HPACK算法對header的數(shù)據(jù)進(jìn)行壓縮,這樣數(shù)據(jù)體積小了,在網(wǎng)絡(luò)上傳輸就會更快。
服務(wù)器推送
服務(wù)端推送是一種在客戶端請求之前發(fā)送數(shù)據(jù)的機制。網(wǎng)頁使用了許多資源:HTML、樣式表、腳本、圖片等等。在HTTP1.1中這些資源每一個都必須明確地請求。這是一個很慢的過程。瀏覽器從獲取HTML開始,然后在它解析和評估頁面的時候,增量地獲取更多的資源。因為服務(wù)器必須等待瀏覽器做每一個請求,網(wǎng)絡(luò)經(jīng)常是空閑的和未充分使用的。
為了改善延遲,HTTP2.0引入了server push,它允許服務(wù)端推送資源給瀏覽器,在瀏覽器明確地請求之前,免得客戶端再次創(chuàng)建連接發(fā)送請求到服務(wù)器端獲取。這樣客戶端可以直接從本地加載這些資源,不用再通過網(wǎng)絡(luò)。