HTTP 緩存機(jī)制 一 二 三
Web 緩存大致可以分為:數(shù)據(jù)庫緩存、服務(wù)器端緩存(代理服務(wù)器緩存、CDN 緩存)、瀏覽器緩存。
瀏覽器緩存也包含很多內(nèi)容: HTTP 緩存、indexDB、cookie、localstorage 等等。這里我們只討論 HTTP 緩存相關(guān)內(nèi)容。
在具體了解 HTTP 緩存之前先來明確幾個(gè)術(shù)語:
- 緩存命中率:從緩存中得到數(shù)據(jù)的請(qǐng)求數(shù)與所有請(qǐng)求數(shù)的比率。理想狀態(tài)是越高越好。
- 過期內(nèi)容:超過設(shè)置的有效時(shí)間,被標(biāo)記為“陳舊”的內(nèi)容。通常過期內(nèi)容不能用于回復(fù)客戶端的請(qǐng)求,必須重新向源服務(wù)器請(qǐng)求新的內(nèi)容或者驗(yàn)證緩存的內(nèi)容是否仍然準(zhǔn)備。
- 驗(yàn)證:驗(yàn)證緩存中的過期內(nèi)容是否仍然有效,驗(yàn)證通過的話刷新過期時(shí)間。
- 失效:失效就是把內(nèi)容從緩存中移除。當(dāng)內(nèi)容發(fā)生改變時(shí)就必須移除失效的內(nèi)容。
瀏覽器緩存主要是 HTTP 協(xié)議定義的緩存機(jī)制。HTML meta 標(biāo)簽,例如
- <META HTTP-EQUIV="Pragma" CONTENT="no-cache">
含義是讓瀏覽器不緩存當(dāng)前頁面。但是代理服務(wù)器不解析 HTML 內(nèi)容,一般應(yīng)用廣泛的是用 HTTP 頭信息控制緩存。
HTTP 頭信息控制緩存
大致分為兩種:強(qiáng)緩存和協(xié)商緩存。強(qiáng)緩存如果命中緩存不需要和服務(wù)器端發(fā)生交互,而協(xié)商緩存不管是否命中都要和服務(wù)器端發(fā)生交互,強(qiáng)制緩存的優(yōu)先級(jí)高于協(xié)商緩存。具體內(nèi)容下文介紹。
匹配流程(已有緩存的情況下):

強(qiáng)緩存
可以理解為無須驗(yàn)證的緩存策略。對(duì)強(qiáng)緩存來說,響應(yīng)頭中有兩個(gè)字段 Expires/Cache-Control 來表明規(guī)則。
Expires
Expires 指緩存過期的時(shí)間,超過了這個(gè)時(shí)間點(diǎn)就代表資源過期。有一個(gè)問題是由于使用具體時(shí)間,如果時(shí)間表示出錯(cuò)或者沒有轉(zhuǎn)換到正確的時(shí)區(qū)都可能造成緩存生命周期出錯(cuò)。并且 Expires 是 HTTP/1.0 的標(biāo)準(zhǔn),現(xiàn)在更傾向于用 HTTP/1.1 中定義的 Cache-Control。兩個(gè)同時(shí)存在時(shí)也是 Cache-Control 的優(yōu)先級(jí)更高。
Cache-Control
Cache-Control 可以由多個(gè)字段組合而成,主要有以下幾個(gè)取值:
1. max-age 指定一個(gè)時(shí)間長(zhǎng)度,在這個(gè)時(shí)間段內(nèi)緩存是有效的,單位是s。例如設(shè)置 Cache-Control:max-age=31536000,也就是說緩存有效期為(31536000 / 24 / 360)天,第一次訪問這個(gè)資源的時(shí)候,服務(wù)器端也返回了 Expires 字段,并且過期時(shí)間是一年后。

在沒有禁用緩存并且沒有超過有效時(shí)間的情況下,再次訪問這個(gè)資源就命中了緩存,不會(huì)向服務(wù)器請(qǐng)求資源而是直接從瀏覽器緩存中取。

2. s-maxage 同 max-age,覆蓋 max-age、Expires,但僅適用于共享緩存,在私有緩存中被忽略。
3. public 表明響應(yīng)可以被任何對(duì)象(發(fā)送請(qǐng)求的客戶端、代理服務(wù)器等等)緩存。
4. private 表明響應(yīng)只能被單個(gè)用戶(可能是操作系統(tǒng)用戶、瀏覽器用戶)緩存,是非共享的,不能被代理服務(wù)器緩存。
5. no-cache 強(qiáng)制所有緩存了該響應(yīng)的用戶,在使用已緩存的數(shù)據(jù)前,發(fā)送帶驗(yàn)證器的請(qǐng)求到服務(wù)器。不是字面意思上的不緩存。
6. no-store 禁止緩存,每次請(qǐng)求都要向服務(wù)器重新獲取數(shù)據(jù)。
協(xié)商緩存
緩存的資源到期了,并不意味著資源內(nèi)容發(fā)生了改變,如果和服務(wù)器上的資源沒有差異,實(shí)際上沒有必要再次請(qǐng)求??蛻舳撕头?wù)器端通過某種驗(yàn)證機(jī)制驗(yàn)證當(dāng)前請(qǐng)求資源是否可以使用緩存。
瀏覽器第一次請(qǐng)求數(shù)據(jù)之后會(huì)將數(shù)據(jù)和響應(yīng)頭部的緩存標(biāo)識(shí)存儲(chǔ)起來。再次請(qǐng)求時(shí)會(huì)帶上存儲(chǔ)的頭部字段,服務(wù)器端驗(yàn)證是否可用。如果返回 304 Not Modified,代表資源沒有發(fā)生改變可以使用緩存的數(shù)據(jù),獲取新的過期時(shí)間。反之返回 200 就相當(dāng)于重新請(qǐng)求了一遍資源并替換舊資源。
Last-modified/If-Modified-Since
Last-modified: 服務(wù)器端資源的最后修改時(shí)間,響應(yīng)頭部會(huì)帶上這個(gè)標(biāo)識(shí)。第一次請(qǐng)求之后,瀏覽器記錄這個(gè)時(shí)間,再次請(qǐng)求時(shí),請(qǐng)求頭部帶上 If-Modified-Since 即為之前記錄下的時(shí)間。服務(wù)器端收到帶 If-Modified-Since 的請(qǐng)求后會(huì)去和資源的最后修改時(shí)間對(duì)比。若修改過就返回最新資源,狀態(tài)碼 200,若沒有修改過則返回 304。

注意:如果響應(yīng)頭中有 Last-modified 而沒有 Expire 或 Cache-Control 時(shí),瀏覽器會(huì)有自己的算法來推算出一個(gè)時(shí)間緩存該文件多久,不同瀏覽器得出的時(shí)間不一樣,所以 Last-modified 要記得配合 Expires/Cache-Control 使用。
Etag/If-None-Match
由服務(wù)器端上生成的一段 hash 字符串,第一次請(qǐng)求時(shí)響應(yīng)頭帶上 ETag: abcd,之后的請(qǐng)求中帶上 If-None-Match: abcd,服務(wù)器檢查 ETag,返回 304 或 200。

關(guān)于 last-modified 和 Etag 區(qū)別,已經(jīng)有很多人總結(jié)過了:
- 某些服務(wù)器不能精確得到資源的最后修改時(shí)間,這樣就無法通過最后修改時(shí)間判斷資源是否更新。
- Last-modified 只能精確到秒。
- 一些資源的最后修改時(shí)間改變了,但是內(nèi)容沒改變,使用 Last-modified 看不出內(nèi)容沒有改變。
- Etag 的精度比 Last-modified 高,屬于強(qiáng)驗(yàn)證,要求資源字節(jié)級(jí)別的一致,優(yōu)先級(jí)高。如果服務(wù)器端有提供 ETag 的話,必須先對(duì) ETag 進(jìn)行 Conditional Request。
注意:實(shí)際使用 ETag/Last-modified 要注意保持一致性,做負(fù)載均衡和反向代理的話可能會(huì)出現(xiàn)不一致的情況。計(jì)算 ETag 也是需要占用資源的,如果修改不是過于頻繁,看自己的需求用 Cache-Control 是否可以滿足。
選擇 Cache-Control 的策略(摘自 Google Developers)

實(shí)際應(yīng)用
回到實(shí)際應(yīng)用上來,首先要明確哪些內(nèi)容適合被緩存哪些不適合。
考慮緩存的內(nèi)容:
- css樣式文件
- js文件
- logo、圖標(biāo)
- html文件
- 可以下載的內(nèi)容
一些不應(yīng)該被緩存的內(nèi)容:
- 業(yè)務(wù)敏感的 GET 請(qǐng)求
可緩存的內(nèi)容又分為幾種不同的情況:
不經(jīng)常改變的文件:
給 max-age 設(shè)置一個(gè)較大的值,一般設(shè)置 max-age=31536000
比如引入的一些第三方文件、打包出來的帶有 hash 后綴 css、js 文件。一般來說文件內(nèi)容改變了,會(huì)更新版本號(hào)、hash 值,相當(dāng)于請(qǐng)求另一個(gè)文件。
標(biāo)準(zhǔn)中規(guī)定 max-age 的值最大不超過一年,所以設(shè)成 max-age=31536000。至于過期內(nèi)容,緩存區(qū)會(huì)將一段時(shí)間沒有使用的文件刪除掉。
有看到用對(duì)話的形式來描述這個(gè)過程,便仿照著試圖更清晰地解釋:


可能經(jīng)常需要變動(dòng)的文件:
Cache-Control: no-cache / max-age=0
比如入口 index.html 文件、文件內(nèi)容改變但名稱不變的資源。選擇 ETag 或 Last-Modified 來做驗(yàn)證,在使用緩存資源之前一定會(huì)去服務(wù)器端做驗(yàn)證,命中緩存時(shí)會(huì)比第一種情況慢一點(diǎn)點(diǎn),畢竟還要發(fā)請(qǐng)求進(jìn)行通信。


注意: 這里只描述了最基本的思路,實(shí)際使用 HTTP 緩存需要后端配合配置,具體情況具體對(duì)待,而且各方的實(shí)現(xiàn)并不一定完全按照標(biāo)準(zhǔn)來的,踩踩坑更健康。