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