圖解 HTTP 的緩存機(jī)制

Hi,大家好奄喂,我是承香墨影铐殃!

HTTP 協(xié)議在網(wǎng)絡(luò)知識中占據(jù)了重要的地位,HTTP 協(xié)議最基礎(chǔ)的就是請求和響應(yīng)的報文頭(Header)跨新,大多數(shù) Http 協(xié)議的使用方式富腊,都是依賴設(shè)置不同的 HTTP 請求/響應(yīng) 的 Header 來實現(xiàn)的。

本系列《實用 HTTP》就拋開常規(guī)的 Header 講解式的表述方式域帐,從實際問題出發(fā)赘被,來分析這些 Http 協(xié)議的使用方式,到底是為了解決什么問題肖揣?同時講解它是如何設(shè)計的和它實現(xiàn)原理民假。

HTTP 協(xié)議是一種無狀態(tài)的“松散協(xié)議”,它不會記錄不同請求的狀態(tài)龙优,并且因為它本身包含了兩端(客戶端和服務(wù)端)羊异,根據(jù)請求和響應(yīng)來區(qū)分,它大部分的內(nèi)容都只是一個建議彤断,其實雙邊是可以不遵守此建議的野舶。例如:服務(wù)端說,這個數(shù)據(jù)緩存有一天的時效性瓦糟,但是客戶端可以說筒愚,我不聽我不聽,我就要每次去重新請求菩浙。

“這里寫了建議零售價 2 元…”

“哦,不接受建議!”

說到緩存劲蜻,本文就來說說 HTTP 緩存相關(guān)的內(nèi)容陆淀。

二、HTTP緩存使用

2.1 為什么需要緩存

緩存說白了就是為了快先嬉,無論是從磁盤到內(nèi)存還是從網(wǎng)絡(luò)到本地轧苫,都是為了在下次實用此資源的時候,能夠快速響應(yīng)疫蔓,避免多次的 I/O 操作含懊。

通過網(wǎng)絡(luò)獲取資源,是一件耗時的操作衅胀,較大的資源還會需要客戶端和服務(wù)端之間進(jìn)行多次往返通信岔乔,這不但會增加客戶端響應(yīng)的時間,同時還會增加網(wǎng)絡(luò)流量滚躯。

在 HTTP 協(xié)議中雏门,天然就有對緩存的支持,瀏覽器和 App 使用的開源網(wǎng)絡(luò)庫中掸掏,都是利用 HTTP 緩存來實現(xiàn)對資源的緩存茁影。

瀏覽器是天然支持 HTTP 緩存,開源庫則需要進(jìn)行一些例如存規(guī)則和緩存的資源存放路徑之類的簡單設(shè)定丧凤。

2.2 設(shè)計一個緩存策略

那如果讓我們來設(shè)計緩存的策略募闲,首先有兩個重要的指標(biāo)需要考慮。

  1. 緩存失效

既然緩存主要是針對數(shù)據(jù)的復(fù)用愿待,那我們就需要有一個條件來判定當(dāng)前緩存的數(shù)據(jù)浩螺,是否依然有效。

總是不能一次緩存呼盆,終身使用吧年扩,我們還需要在緩存失效之后,重新獲取新的數(shù)據(jù)并進(jìn)行緩存访圃。這個前提就是厨幻,緩存都需要有一個失效的策略。

  1. 減少讀取

雖然緩存會有失效策略腿时,但是這只是客戶端單方面認(rèn)為失效况脆,此時應(yīng)該再去服務(wù)端重新獲取一遍數(shù)據(jù)。

可有些情況下批糟,其實資源可能依然有效格了,并沒有發(fā)生變動。那就需要有一個策略徽鼎,讓服務(wù)端通知客戶端盛末,當(dāng)前緩存依然有效弹惦,可以繼續(xù)使用。這樣在減少傳輸流量之外悄但,也可以加快相應(yīng)時間棠隐,提高效率。

這就是一個好的緩存策略必須要考慮的地方檐嚣,實際上 HTTP 緩存助泽,也是這樣設(shè)計的。

2.3 HTTP 緩存

HTTP 緩存主要是通過請求和響應(yīng)報文頭中的對應(yīng) Header 信息嚎京,來控制緩存的策略嗡贺。

這里主要涉及兩個 Header:

  • Cache-Control:設(shè)定緩存策略,是否使用緩存鞍帝,超時時間是多少诫睬。

  • ETag:當(dāng)前返回數(shù)據(jù)的驗證令牌,可能是 Hash 值也可能是其他指紋膜眠,主要用于在下次請求的時候攜帶上岩臣,讓服務(wù)端依此判斷當(dāng)前數(shù)據(jù)是否有更改。


    image.png

服務(wù)端在返回響應(yīng)數(shù)據(jù)的時候宵膨,會在報文頭中架谎,增加用于描述當(dāng)前響應(yīng)的內(nèi)容類型、數(shù)據(jù)長度辟躏、緩存策略(Cache-Control)谷扣、驗證令牌(ETag)等信息。

例如上圖就表示了一次請求響應(yīng)的事務(wù)捎琐,大概客戶端請求一個文件的時候会涎,服務(wù)端返回了一個 200 的狀態(tài)碼,表示響應(yīng)正常瑞凑,響應(yīng)的數(shù)據(jù)長度為 1024 個字節(jié)末秃,建議客戶端將此資源緩存最多 120 秒,并且提供了一個指紋令牌(“cxmyDev123”)籽御,用來作為當(dāng)前數(shù)據(jù)的唯一標(biāo)識练慕。

2.4 ETag 數(shù)據(jù)令牌

Cache-Control 中設(shè)定的 max-age 很好理解,就是設(shè)定緩存超時的時間技掏,HTTP 緩存是限定一個超時的秒數(shù)铃将,來確定緩存失效的時間。

上古時期還會使用 expires 來決定超時的日期哑梳,但是已經(jīng)被廢棄了劲阎,如果和 Cache-Control 同時存在,以 Cache-Control 為準(zhǔn)鸠真。

在此時間間隔范圍內(nèi)悯仙,客戶端不會再向服務(wù)端發(fā)送新的請求龄毡。當(dāng)資源距離上一次緩存的時間間隔,大于 120 秒后雁比,客戶端才會再次向服務(wù)端發(fā)送請求稚虎。

假如沒有數(shù)據(jù)令牌的情況下撤嫩,大概步驟應(yīng)該是這樣的:

  1. 客戶端會首先找到本地緩存偎捎,然后發(fā)現(xiàn)它已經(jīng)失效,無法再次使用序攘。

  2. 客戶端再次向服務(wù)端發(fā)出新的請求茴她,并獲取完整的數(shù)據(jù)再次進(jìn)行緩存。之后再刷新該緩存的超時時間程奠。

但是這是一件效率非常低的事情丈牢,服務(wù)端并無法確定所持有的源資源什么時候會失效,所以提供的 max-age 值瞄沙,只是一個參考值己沛,是需要取平衡的,太短會導(dǎo)致請求頻繁距境,太長又會導(dǎo)致無法及時刷新客戶端資源申尼。而此時再次請求的時候,是存在一定的概率垫桂,客戶端緩存的數(shù)據(jù)和服務(wù)端上持有的數(shù)據(jù)是一致的师幕,我們就不需要再次對此數(shù)據(jù)資源進(jìn)行二次緩存,直接使用客戶端之前緩存的數(shù)據(jù)即可诬滩,同時還需要刷新緩存超時時間霹粥。

這正是數(shù)據(jù)驗證令牌(ETag)想要解決的問題,服務(wù)端生成并返回的這個數(shù)據(jù)指紋令牌疼鸟,通常就是返回數(shù)據(jù)的 Hash 值或者其他數(shù)據(jù)指紋后控,客戶端無需關(guān)心它的生成規(guī)則,只需要知道它是當(dāng)前數(shù)據(jù)的一個唯一標(biāo)識空镜。


image.png

客戶端需要在下次請求時將其通過 If-None-Match 這個請求報文頭浩淘,將此驗證令牌發(fā)送至服務(wù)端,如果數(shù)據(jù)令牌指紋和服務(wù)端當(dāng)前的數(shù)據(jù)一致姑裂,則標(biāo)識資源未發(fā)生新的變化馋袜。就會返回一個 304 的狀態(tài)碼,表示可以繼續(xù)使用客戶端本地緩存的數(shù)據(jù)舶斧,并刷新超時時間欣鳖。注意當(dāng)響應(yīng)碼為 304 的時候,它是不包含數(shù)據(jù)內(nèi)容的茴厉。

通常此緩存操作對我們都是透明的泽台,它是瀏覽器和開源網(wǎng)絡(luò)庫的基本實現(xiàn)什荣,我們無需自己去判斷 max-age 和 ETag 的值,這一步我們只需要確定服務(wù)端對此有支持即可怀酷。

這里只是提到了 If-None-Match稻爬,它標(biāo)識比較 ETag 是否不一致,除此之外蜕依,還有一些其他的相關(guān)報文頭桅锄,例如 If-Match,有興趣可以查閱相關(guān)資料样眠。

2.5 Cache-Control

前面舉的例子中友瘤,我們只為 Cache-Control 設(shè)定了一個 max-age,但是其實還有一些更豐富的配置檐束。

從緩存性能最優(yōu)化的角度來看辫秧,最佳的緩存是無需與服務(wù)端通信的緩存,可以通過緩存來消滅網(wǎng)絡(luò)延遲以及數(shù)據(jù)請求被丧,從而來提高用戶的體驗盟戏。

Cache-Control 是在 HTTP/1.1 中被定義的,它可以用于取代之前的緩存策略甥桂,現(xiàn)在所有的瀏覽器都支持 Cache-Control 柿究,它已經(jīng)成為一種通用的標(biāo)準(zhǔn)。


image.png

Cache-Control 還有一些更靈活的配置格嘁,用來對緩存做一些更細(xì)致的操作笛求。

  1. “no-cache” 和 “no-store”

這兩個參數(shù)都表示每一次請求,都需要真實的發(fā)送一個網(wǎng)絡(luò)請求糕簿。

它們之間的區(qū)別在于探入,“no-cache”并不是真的不緩存數(shù)據(jù),它只是要求每次都確認(rèn)資源是否過期懂诗,也就是它會利用數(shù)據(jù)令牌 ETag 來一定程度的減小傳輸?shù)牧髁俊?/p>

而 “no-store” 完全是要求客戶端蜂嗽,每次都重新請求數(shù)據(jù)并下載最新的數(shù)據(jù),不做任何緩存處理殃恒。這種不緩存的策略植旧,也包括中間連接的代理、網(wǎng)關(guān) 等中間傳輸?shù)耐ǖ览胩疲惨徊⒉粚?shù)據(jù)進(jìn)行緩存病附,每次都從源服務(wù)器上獲取數(shù)據(jù)。

  1. “public” 和 “private”

“public” 是一種默認(rèn)的策略亥鬓,表示當(dāng)前緩存是開放的完沪,任何請求響應(yīng)的中間環(huán)節(jié),都可以對其進(jìn)行緩存,如果我們不顯式指定覆积,則當(dāng)前為 “public” 緩存听皿。

與之相對的 “private”,則表示當(dāng)前響應(yīng)是針對單個用戶的宽档,并非通用數(shù)據(jù)尉姨,因此不建議任何中間緩存對其進(jìn)行緩存。例如:瀏覽器就是一個比較私人的緩存源吗冤,它會緩存 “private” 的緩存又厉,而 CDN 則不會。

三欣孤、最佳的緩存策略樹

前面提到馋没,緩存的核心目的就是為了快,能讓下次使用的時候快速復(fù)用降传。所以在理想情況下,我們應(yīng)該將響應(yīng)數(shù)據(jù)盡可能多的緩存勾怒,盡可能的緩存足夠長的時間婆排,并且為每個資源提供單獨的數(shù)據(jù)驗證令牌,以便在時間過期之后快速校驗笔链。

但是任何事情都是要取其平衡點的段只,不存在什么最佳緩存策略,并非所有響應(yīng)資源都需要加緩存鉴扫,這就需要根據(jù)業(yè)務(wù)場景來設(shè)定赞枕。

這里給出一個增加 HTTP 緩存的通用策略樹,你在對響應(yīng)增加緩存的時候坪创,可以參考它來執(zhí)行炕婶。


image.png

正常情況下,我們針對不同的響應(yīng)屬性莱预,會對它設(shè)置不同的緩存策略柠掂,下面根據(jù)場景,舉幾個例子依沮。

3.1 用戶相關(guān)的數(shù)據(jù)

和單個用戶緊密相關(guān)的數(shù)據(jù)涯贞,通常我們是不建議使用緩存的,但是依然存在幾個等級危喉。

  1. 嚴(yán)格不使用緩存
Cache-Control:no-store
  1. 允許客戶終端緩存宋渔,但是每次使用都需要確認(rèn)
Cache-Control:no-cache
ETag:"cxmyDev1234"
  1. 允許客戶終端短時間緩存
Cache-Control:private max-age=600
ETag:"cxmyDev1234"

3.2 通用數(shù)據(jù)
一些通用響應(yīng)資源,更新的頻率非常的低辜限,我們可以根據(jù)需要調(diào)整 max-age 的大小即可皇拣。

Cache-Control:max-age=86400
ETag:"cxmyDev1234"

四、廢棄和更新緩存的響應(yīng)

緩存的策略列粪,一旦確定并下發(fā)到客戶端审磁,服務(wù)端就失去了對齊的控制權(quán)谈飒。也就是說,如果我們設(shè)定了 max-age态蒂,在此資源有效期超時之前杭措,哪怕服務(wù)端的源資源已經(jīng)被替換修改,我們也沒有一個合適的時機(jī)去通知客戶端更新新的響應(yīng)數(shù)據(jù)钾恢。

那么有沒有什么好的策略去標(biāo)記資源廢棄手素?同時又能友好的利用緩存策略。

在互聯(lián)網(wǎng)上瘩蚪,所有服務(wù)上的資源泉懦,都有一個對應(yīng)的 URL(統(tǒng)一資源定位符),它可以明確說明如何從一個精確且固定的位置獲取資源疹瘦。而 HTTP 緩存崩哩,也是依賴于 URL 的,注意 URL 是大小寫敏感的言沐,同一個 URL 表示同一個請求響應(yīng)邓嘹,依此來判斷緩存和后續(xù)緩存的復(fù)用。

所以我們是可以在 URL 上做文章的险胰。

4.1 瀏覽器的廢棄策略

前面提到汹押,瀏覽器是天然支持 HTTP 緩存的,對于瀏覽器來說起便,它所面對的就是一個個 HTML 頁面棚贾,頁面內(nèi)會包含一些 CSS、Image榆综、JavaScript妙痹、JSON 資源和數(shù)據(jù)。

針對不同的資源和數(shù)據(jù)奖年,我們可以在其 URL 上细诸,增加數(shù)據(jù)令牌指紋,當(dāng)資源變動的時候陋守,同時也去刷新改指紋令牌震贵。


image.png

到這里就很好理解了:

  • HTML 頁面,使用 no-cache水评,強(qiáng)制每次都向源服務(wù)器確認(rèn)數(shù)據(jù)猩系。
  • CSS文件通常變動的頻率非常低,所以可以允許中間層緩存中燥,并且緩存時間為一年不過期寇甸。
  • JavaScript內(nèi)有業(yè)務(wù)邏輯,可以設(shè)定為只允許客戶終端緩存。
  • getUserInfo拿霉,是為個人用戶數(shù)據(jù)相關(guān)吟秩,這里推薦可緩存,但是需要每次向服務(wù)器重新確認(rèn)绽淘。

4.2 App 接口的緩存策略

在 App 中使用的接口涵防,其實和網(wǎng)頁又不一樣,HTML 網(wǎng)頁的結(jié)構(gòu)類似一個樹形結(jié)構(gòu)沪铭,先通過獲取 .html 文件獲取其內(nèi)所有資源的表壮池,然后依次根據(jù)緩存策略進(jìn)行訪問。

但是在 App 中杀怠,和服務(wù)器的交互都是通過數(shù)據(jù)接口來實現(xiàn)的椰憋,就不存在最開始獲取一個類似 HTML 文件這樣的樹形接口,每個接口都是一個個“孤島”赔退,可以單獨存在橙依。我們就無法提前知道某個接口的響應(yīng)數(shù)據(jù)已經(jīng)過期,同時也無法修改 URL 上攜帶的數(shù)據(jù)指紋令牌离钝。

但是其實我們是可以通過 App 和設(shè)備的一些固有信息票编,作為 URL 的參數(shù)傳遞,以此來刷新數(shù)據(jù)卵渴。


image.png

例如這里 /app/main 獲取主頁的數(shù)據(jù),這里將當(dāng)前 App 的版本號當(dāng)參數(shù)拼接在 URL 的后面鲤竹,以此方式來強(qiáng)制不同的版本浪读,刷新不同的數(shù)據(jù)。避免剛升級上來的 App辛藻,還在使用舊版本的數(shù)據(jù)碘橘。

這個例子中,版本號只是其中一個維度吱肌,如果有必要痘拆,還可以傳遞其他維度的信息,例如當(dāng)前網(wǎng)絡(luò)狀態(tài)氮墨,當(dāng)前用戶 id 等等纺蛆。

五、小結(jié)

到這里我們基本上把 HTTP 的緩存所有相關(guān)的內(nèi)容都講了一遍规揪,這里簡單總結(jié)一下诗鸭。

  • HTTP 緩存依賴 URL 做唯一標(biāo)識疫粥,不同的 URL 使用不同的緩存。

  • Cache-Control 可以控制緩存策略,共有或者私有杂拨、緩存超時時長等忙菠。

  • 通過 ETag 來標(biāo)記數(shù)據(jù)指紋令牌,以此來確定響應(yīng)數(shù)據(jù)是否更新。

  • 應(yīng)該為每個響應(yīng)資源提供對應(yīng)的緩存策略揖庄。

  • 如果需要廢棄之前的緩存,可以利用修改請求 URL 的方式欠雌,將數(shù)據(jù)指紋令牌追加在 URL 之后蹄梢,以此來更新數(shù)據(jù)。

關(guān)于 HTTP 緩存桨昙,你還有什么更好的想法检号,可以在留言區(qū)討論。

參考資料:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蛙酪,一起剝皮案震驚了整個濱河市齐苛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌桂塞,老刑警劉巖凹蜂,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異阁危,居然都是意外死亡玛痊,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進(jìn)店門狂打,熙熙樓的掌柜王于貴愁眉苦臉地迎上來擂煞,“玉大人,你說我怎么就攤上這事趴乡《允。” “怎么了?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵晾捏,是天一觀的道長蒿涎。 經(jīng)常有香客問我,道長惦辛,這世上最難降的妖魔是什么劳秋? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮胖齐,結(jié)果婚禮上玻淑,老公的妹妹穿的比我還像新娘。我一直安慰自己市怎,他們只是感情好岁忘,可當(dāng)我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著区匠,像睡著了一般干像。 火紅的嫁衣襯著肌膚如雪帅腌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天麻汰,我揣著相機(jī)與錄音速客,去河邊找鬼。 笑死五鲫,一個胖子當(dāng)著我的面吹牛溺职,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播位喂,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼浪耘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了塑崖?” 一聲冷哼從身側(cè)響起七冲,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎规婆,沒想到半個月后澜躺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡抒蚜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年掘鄙,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嗡髓。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡操漠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出饿这,到底是詐尸還是另有隱情颅夺,我是刑警寧澤,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布蛹稍,位于F島的核電站,受9級特大地震影響部服,放射性物質(zhì)發(fā)生泄漏唆姐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一廓八、第九天 我趴在偏房一處隱蔽的房頂上張望奉芦。 院中可真熱鬧,春花似錦剧蹂、人聲如沸声功。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽先巴。三九已至其爵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間伸蚯,已是汗流浹背摩渺。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留剂邮,地道東北人摇幻。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像挥萌,于是被迫代替她去往敵國和親绰姻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,492評論 2 348

推薦閱讀更多精彩內(nèi)容