Web緩存分為很多種,比如數(shù)據(jù)庫緩存,代理服務(wù)器緩存,還有我們熟悉的CDN緩存,以及瀏覽器緩存。
緩存的重要性不用多說了吧,例如chrome會把緩存的文件保存在一個叫User Data的文件夾下,下次頁面加載同樣資源時,可以直接從本地讀取,不必通過HTTP從服務(wù)器端再次下載資源,大大加快了頁面的響應(yīng)速度。
本篇主要介紹一下瀏覽器緩存:
- HTTP Rresponse Header
- Cache-Control
- Expires
- Last-modified
- ETag
- 實時更新緩存
- 緩存策略
Cache-Control
Cache-Control用于定義資源的緩存策略??梢灾付?code>no-store,no-cache,public,private,max-gae,s-maxage
no-store絕對禁止緩存,每次請求資源都要從服務(wù)器重新獲取。常見于包含隱私數(shù)據(jù)或銀行數(shù)據(jù)等場合。
no-cache表示不管max-age過不過期,每次都要向服務(wù)器重新驗證資源是否被更改。只有服務(wù)器端確認(rèn)資源未被修改后(如后面會介紹的Last-modified或ETag),才能使用本地緩存。
public(默認(rèn)就是public)允許代理服務(wù)器緩存資源,以供多用戶間共享。
private不允許代理服務(wù)器緩存該資源,用戶間不共享,如HTTP認(rèn)證響應(yīng)會自動設(shè)為private
max-age緩存最大有效時間。單位是秒,從被請求時開始計時,這樣可以避免時鐘同步問題。因為原先用于緩存的Expires需要服務(wù)器和客戶端時鐘嚴(yán)格同步,因此HTTP 1.1引入了cache-control: max-age來避免時鐘同步的限制。因此當(dāng)cache-control: max-age和Expires同時出現(xiàn),Expires將被忽略。
s-maxage只適用于共享緩存如CDN緩存。
如圖服務(wù)器端收到請求后,在HTTP Rresponse Header里將cache-control: max-age=120,表示該資源的有效時間為2分鐘。2分鐘內(nèi)客戶端如需再次使用該資源,可以直接從本地讀取,不需要再次向服務(wù)器發(fā)出請求了。

Cache-Control的判斷流程圖:

Expires
在HTTP1.1引入Cache-Control之前,用Expires設(shè)置緩存過期時間,告訴瀏覽器在過期時間前瀏覽器可以直接從本地讀取資源,不必再次向服務(wù)器請求。
但HTTP1.1之后用Cache-Control取代了Expires,如果為了兼容性cache-control: max-age和Expires同時出現(xiàn),Expires將被忽略
Last-modified
Last-modified是服務(wù)器告訴瀏覽器這個資源最后被修改的時間。
有什么用呢?例如瀏覽器根據(jù)cache-control: max-age或Expires發(fā)現(xiàn)緩存過期了,按理應(yīng)該向服務(wù)器重新請求資源。但如果資源內(nèi)容其實沒有變,重新請求資源太浪費了,用Last-modified可以優(yōu)化這個過程。
在有Last-Modified的情況下,瀏覽器會發(fā)送If-Modified-Since請求,服務(wù)器端比對請求里L(fēng)ast-modified時間和資源文件最后被修改時間是否一致。如果一致,服務(wù)器端回復(fù)304 Not Modified,這樣瀏覽器就能繼續(xù)使用本地的過期緩存了,節(jié)約了帶寬。如果不一致,服務(wù)器回復(fù)200 OK,重新發(fā)送資源給瀏覽器。
Last-modified作為一種優(yōu)化手段,需要和cache-control: max-age或Expires共同使用。
ETag
Last-modified作為一種優(yōu)化手段,還不夠完美,有時會有一些問題:
- 某些服務(wù)器不能精確得到資源的最后修改時間
- 如果資源修改非常頻繁,在秒以下的時間內(nèi)進(jìn)行修改,而Last-modified只能精確到秒
- 一些資源的最后修改時間變了,但其實內(nèi)容沒改變
因此引入了ETag作為Last-modified的進(jìn)階版來解決上面這些問題。服務(wù)端根據(jù)實體內(nèi)容生成一段hash字符串,用以標(biāo)識資源的狀態(tài)。具體生成的hash字符串服務(wù)器有自己的規(guī)范,如Apache中,默認(rèn)是對文件的索引節(jié)(INode),大小(Size)和最后修改時間(MTime)進(jìn)行hash后得到的。例如Etag: “5483ec7b-7c52”
ETag的原理和Last-modified類似,雖然瀏覽器根據(jù)cache-control: max-age或Expires發(fā)現(xiàn)緩存過期了,但如果資源其實沒有變的話,重新請求有點浪費。用ETag可以優(yōu)化這個過程。
在有ETag的情況下,瀏覽器會發(fā)送If-None-Match請求,服務(wù)器端將請求里ETag字符串和資源文件的ETag字符串進(jìn)行比較。如果一致,服務(wù)器端回復(fù)304 Not Modified,這樣瀏覽器就能繼續(xù)使用本地的過期緩存了,節(jié)約了帶寬。如果不一致,服務(wù)器回復(fù)200 OK,重新發(fā)送資源給瀏覽器。
ETag作為Last-modified的進(jìn)階版,同樣需要和cache-control: max-age或Expires共同使用。而且當(dāng)ETag和Last-modified同時存在時,Last-modified會被忽略,因此如果ETag匹配失敗,即使緩存的Last-Modified沒失效,也得到不到304 Not Modified。
如下圖,瀏覽器發(fā)現(xiàn)cache-control: max-age=120過期了,就向服務(wù)器發(fā)送If-None-Match請求,服務(wù)器發(fā)現(xiàn)資源未被修改過,因此回復(fù)304 Not Modified通知瀏覽器繼續(xù)使用本地緩存

ETag的問題在于使用網(wǎng)站服務(wù)器會使用資源的某些屬性來構(gòu)造它,但對于擁有多臺服務(wù)器的網(wǎng)站,如Apache和IIS使用的屬性不同,導(dǎo)致生成的hash值不同,會大大降低驗證的成功率。結(jié)果是,對于完全相同的資源,當(dāng)瀏覽器從Apache上獲取了資源,又向IIS發(fā)起Get請求,ETag是不會匹配的。用戶就無法收到更小更快的304,而是收到200正常去下載該資源。
如在多臺服務(wù)器上寄宿你的網(wǎng)站,且你使用默認(rèn)ETag配置的Apache或IIS,那么效率問題你必須面對。例如Apache用FileETag可從ETag中移除inode值,只留下大小和時間撮作為組件的ETag。IIS也有類似的ChangeNumber只留下時間撮作為組件的ETag。當(dāng)然也可以徹底移除ETag,如Apache上FileETag none
整體緩存的過程:

實時更新緩存
如果你改動了某資源,如CSS,但用戶本地的cache-control: max-age或Expires時間未到,用戶仍舊會使用舊的CSS。除非緩存過期,或用戶清理了瀏覽器緩存,否則你修改的資源文件是沒有辦法第一時間通知到用戶的。
如果想要實現(xiàn),只能修改資源文件路徑,例如將CSS文件名重命名為xxx-v2.css等。以此強(qiáng)制用戶重新加載最新版資源。
緩存策略
可以為不同類型的資源文件定制不同的緩存策略,如下圖:

HTML文件必須確保最新,因此定義成no-cache,這樣每次請求都會驗證該HTML文件是否最新
JS和CSS文件因為經(jīng)常會被修改,因此文件名嵌入指紋碼(也可以是版本號或時間戳)。每次修改文件后文件名均不同,相當(dāng)于HTML里加載不同的文件,強(qiáng)制用戶下載最新版。由于文件名里嵌入了指紋碼,可以放心大膽地將max-age設(shè)置1年。
JS里標(biāo)記為private,因為JS里可能會包含一些私人數(shù)據(jù)。
圖片因為不常變,所以文件名不必包含指紋碼,可以根據(jù)需要設(shè)置max-age
最后配合ETag可以使得緩存機(jī)制更高效。