動(dòng)畫:深入淺出從根上理解 HTTP 緩存機(jī)制及原理!
HTTP 緩存,對(duì)于前端的性能優(yōu)化方面來講,是非常關(guān)鍵的,從緩存中讀取數(shù)據(jù)和直接向服務(wù)器請(qǐng)求數(shù)據(jù),完全就是一個(gè)在天上,一個(gè)在地下。
我們最熟悉的是 HTTP 服務(wù)器響應(yīng)返回狀態(tài)碼 304,304 代表表示告訴瀏覽器,本地有緩存數(shù)據(jù),可直接從本地獲取,無需從服務(wù)器獲取浪費(fèi)時(shí)間。
至于為什么被緩存,如何命中緩存以及緩存什么時(shí)候生效的,我們卻很少在實(shí)際開發(fā)中去了解。今天小鹿借助動(dòng)畫形式來從根上理解 HTTP 緩存機(jī)制及原理。
為什么會(huì)有緩存?
單純的從計(jì)算機(jī)角度去說,比較抽象,咱們看一個(gè)實(shí)際的例子。比如,我們通常喜歡把沒看完的書放在書架上,而看完以及沒有看的書放在箱子中保存。
如果我們把所有的書保存在箱子中,每次看書都要去箱子中找,所以非常麻煩和耗時(shí)(這里的箱子,可以想象成服務(wù)器)。
當(dāng)我們開始看新書時(shí),第一次從箱子中取出,看了一半,然后我們直接放到書架上,當(dāng)下次再看書的時(shí)候,直接從書架中取出,這里的書架,就是我們下邊要講到的緩存(一個(gè)緩存?zhèn)}庫(kù))。
緩存的“龜”則
當(dāng)瀏覽器發(fā)出請(qǐng)求到數(shù)據(jù)請(qǐng)求回來的過程,就像是上述中的取書過程。
瀏覽器在加載資源時(shí),根據(jù)請(qǐng)求頭的Expires 和 Cache-control 判斷是否命中強(qiáng)緩存,是則直接從緩存讀取資源,不會(huì)發(fā)請(qǐng)求到服務(wù)器。
如果沒有命中強(qiáng)緩存,瀏覽器一定會(huì)發(fā)送一個(gè)請(qǐng)求到服務(wù)器,通過 Last-Modified 和 Etag 驗(yàn)證資源是否命中協(xié)商緩存,如果命中,服務(wù)器會(huì)將這個(gè)請(qǐng)求返回,但是不會(huì)返回這個(gè)資源的數(shù)據(jù),依然是從緩存中讀取資源。
如果前面兩者都沒有命中,直接從服務(wù)器加載資源。
動(dòng)畫演示

HTTP 緩存分類
上述講到,HTTP 是有“龜”則的,根據(jù)瀏覽器是否向服務(wù)器發(fā)起請(qǐng)求來分為強(qiáng)緩存和協(xié)商緩存。
1、強(qiáng)緩存
強(qiáng)緩存的意思就是不向服務(wù)器發(fā)起請(qǐng)求的緩存,也就是本地強(qiáng)制緩存。瀏覽器想要獲取特定數(shù)據(jù)的時(shí)候,首先會(huì)檢查一下本地的緩存是否存在該數(shù)據(jù),如果存在,就直接在本地獲取了,如果不存在,就向服務(wù)器所要該數(shù)據(jù)。
詳細(xì)請(qǐng)求過程如下動(dòng)畫所示:


那么問題來了,如果我們想使用強(qiáng)緩存,那怎么判斷緩存數(shù)據(jù)什么時(shí)候失效呢?
當(dāng)瀏覽器向服務(wù)器請(qǐng)求數(shù)據(jù)的時(shí)候,服務(wù)器會(huì)將數(shù)據(jù)和緩存的規(guī)則返回,在響應(yīng)頭的 header 中,有兩個(gè)字段 Expires和Cache-Control。
Expires
1expires:?Wed,?11?Sep?2019?16:12:18?GMT
在響應(yīng)頭中 Expires 字段的意思是,當(dāng)前返回?cái)?shù)據(jù)的緩存到期時(shí)間戳。當(dāng)瀏覽器在進(jìn)行請(qǐng)求的時(shí)候,會(huì)那瀏覽器本地的時(shí)候和這個(gè)時(shí)間做對(duì)比,判斷資源是否過期。
但是上述存在一個(gè)問題就是,如果我手動(dòng)改變了電腦的時(shí)間,那么就會(huì)出現(xiàn)問題,這也是 HTTP1.0 中存在的問題。
Cache-Control
為了解決這個(gè)問題,在 HTTP1.1 中增加了 Cache-Control 這個(gè)字段。
1Cache-Control:max-age=7200
服務(wù)器和客戶端說,這個(gè)資源緩存只可以存在 7200 秒,在這個(gè)時(shí)間段之內(nèi),你就可以在緩存獲取資源。
如果 Expire 和 Cache-control 兩者同時(shí)出現(xiàn),則以 Cache-control 為主
除此之外,cache-control 還有其他字段可以使用。
1cache-control:?max-age=3600,?s-maxage=31536000
Public:只要為資源設(shè)置了 public,那么它既可以被瀏覽器緩存,也可以被代理服務(wù)器緩存;
Private(默認(rèn)值):則該資源只能被瀏覽器緩存。
no-store:不使用任何緩存,直接向服務(wù)器發(fā)起請(qǐng)求。
no-cache:繞開瀏覽器緩存(每次發(fā)起請(qǐng)求不會(huì)詢問瀏覽器緩存),而是直接向服務(wù)器確認(rèn)該緩存是夠過期。
2、協(xié)商緩存
瀏覽器第一次請(qǐng)求數(shù)據(jù)時(shí),服務(wù)器會(huì)將緩存標(biāo)識(shí)與數(shù)據(jù)一起返回給客戶端,客戶端將二者備份至緩存數(shù)據(jù)庫(kù)中。
再次請(qǐng)求數(shù)據(jù)時(shí),客戶端將備份的緩存標(biāo)識(shí)發(fā)送給服務(wù)器,服務(wù)器根據(jù)緩存標(biāo)識(shí)進(jìn)行判斷,判斷成功后,返回304狀態(tài)碼,通知客戶端比較成功,可以使用緩存數(shù)據(jù)。


1//?命中緩存的響應(yīng)字段
2Request?Method:GET
3Status?Code:?304?Not?Modified
怎么來識(shí)別協(xié)商緩存的?主要通過報(bào)文頭部 header 中的Last-Modified,If-Modified-Since 以及ETag、If-None-Match 字段來進(jìn)行識(shí)別。
Last-Modified?
Last-Modified 字段的意思是服務(wù)器資源的最后修改時(shí)間。第一次請(qǐng)求服務(wù)器,服務(wù)器的頭部字段可增加這個(gè)字段,用于設(shè)置協(xié)商緩存。
1Last-Modified:?Fri,?27?Oct?2017?06:35:57?GMT
當(dāng)瀏覽器再次發(fā)起請(qǐng)求的時(shí)候,首部字段增加 If-Modified-Since 本地時(shí)間戳字段發(fā)給服務(wù)器。
1If-Modified-Since:?Fri,?27?Oct?2017?06:35:57?GMT
服務(wù)端接收到請(qǐng)求之后,就拿 If-Modified-Since 字段值和本身的過期時(shí)間對(duì)比。
如果請(qǐng)求頭中的這個(gè)值小于最后修改時(shí)間,返回的 304 響應(yīng),讓其在本地瀏覽器緩存取出數(shù)據(jù)。如果時(shí)間過期,并在 Response Headers中添加新的 Last-Modified 值返回給瀏覽器。
但是 Last-Modified 存在一個(gè)局限性,有以下兩種情況:
不該請(qǐng)求,還會(huì)請(qǐng)求。編輯了文件,文件內(nèi)容沒有變,但是服務(wù)器確認(rèn)為我們改動(dòng)了文件,所以重新設(shè)置了緩存時(shí)間,當(dāng)做新請(qǐng)求返回給瀏覽器。
該請(qǐng)求,反而沒有請(qǐng)求。修改文件速度很快,快過 If-Modified-Since 字段時(shí)間差的檢測(cè),文件雖然改動(dòng)了,但是并沒有重新生成新的資源。
ETag
ETag 代表的意思是標(biāo)識(shí)字符串。由于上述 Last-Modified 字段存在的缺陷,所以在 HTTP / 1.1 ?我們對(duì)資源進(jìn)行內(nèi)容編碼,只要內(nèi)容被改變,這個(gè)編碼就不同。
和上述請(qǐng)求原理一樣,瀏覽器首次發(fā)起請(qǐng)求,然后服務(wù)器在響應(yīng)頭返回一個(gè)標(biāo)識(shí)字符串。
1ETag:?W/"2a3b-1602480f459"瀏覽器再次發(fā)起請(qǐng)求,攜帶一個(gè)值相同的字符串。
1If-None-Match:?W/"2a3b-1602480f459"
服務(wù)端接收到該字符串就會(huì)作對(duì)比,如果相同,則讓其讀取本地緩存,否則,將新的資源返回給瀏覽器端。
緩存位置
緩存的位置按照獲取資源請(qǐng)求優(yōu)先級(jí),緩存位置依次如下:
Memory Cache(內(nèi)存緩存)
Service Worker(離線緩存)
Disk Cache(磁盤緩存)
Push Cache(推送緩存)
Memory Cache
Memory 為內(nèi)存緩存,是瀏覽器最先嘗試命中的緩存,也是響應(yīng)最快的緩存。但是存活時(shí)間最短的,當(dāng)進(jìn)程結(jié)束后,tab 標(biāo)簽關(guān)閉后,緩存就不存在了。

因?yàn)閮?nèi)存空間比較小,通常較小的資源放在內(nèi)存緩存中,比如 base64 圖片等資源。
Service Worker
Service Worker 是一種獨(dú)立于主線程之外的 Javascript 線程。它脫離于瀏覽器窗體,因此無法直接訪問 DOM。
可以幫我們實(shí)現(xiàn)離線緩存、消息推送和網(wǎng)絡(luò)代理等功能。
Disk Cache
內(nèi)存的優(yōu)先性,導(dǎo)致大文件不能緩存到內(nèi)存中,那么磁盤緩存則不同。雖然存儲(chǔ)效率比內(nèi)存緩存慢,但是存儲(chǔ)容量和存儲(chǔ)市場(chǎng)有優(yōu)勢(shì)。
Push Cache
它是最后一道緩存命中,屬于 HTTP2 的內(nèi)容。如果感興趣的同學(xué),可以先去了解了解。
-------------------------
推薦閱讀
