參考資源
- 圖書:《圖解HTTP》
- 博客:[1] AlloyTeam Web緩存機(jī)制系列
- 博客:[2] 淺談瀏覽器http的緩存機(jī)制
- 博客:[3] 瀏覽器緩存知識(shí)小結(jié)及應(yīng)用
- Doc: developers.google.com/working-with-indexdb
關(guān)于緩存的文章能搜索到很多,標(biāo)題通常包含關(guān)鍵字 HTTP緩存、瀏覽器緩存 或 Web緩存。本篇嘗試以易懂的、容易操作方式介紹Web緩存的基礎(chǔ)知識(shí)。
1. 通用概念
1.1 什么是Web緩存
顧名思義Web緩存就是一個(gè)Web資源(html、js、圖片等)在客戶端和服務(wù)器之間的副本。研究Web緩存就是研究它們的機(jī)制,然后在實(shí)際應(yīng)用中通過合理使用Web緩存提高便利性。
1.2 Web緩存的分類
Web緩存的分類可以按照數(shù)據(jù)請求過程分為:
-
應(yīng)用層緩存
- 網(wǎng)頁可以從本地文件中讀取數(shù)據(jù),將常用的數(shù)據(jù)記錄到內(nèi)存中。
- 利用HTML5提供的技術(shù),將數(shù)據(jù)存儲(chǔ)在localStorage等地方。
-
HTTP緩存
- 跟瀏覽器緩存是一個(gè)概念,它是基于HTTP標(biāo)準(zhǔn)協(xié)議實(shí)現(xiàn)的一套機(jī)制。
-
服務(wù)器端緩存
- 分為代理服務(wù)器緩存 和 CDN緩存。
-
數(shù)據(jù)庫緩存
- 對關(guān)系復(fù)雜的是數(shù)據(jù)庫查詢能極大的提高響應(yīng)效率。
其中,HTTP緩存在實(shí)際應(yīng)用受到的關(guān)注最多,也是本篇重點(diǎn)介紹的部分。
1.3 Web緩存的作用
- 減少網(wǎng)絡(luò)帶寬,降低運(yùn)營成本
- 減低服務(wù)器壓力
- 加快頁面響應(yīng)速度,提供給更好的用戶體驗(yàn)
2. 準(zhǔn)備
2.1 HTTP基礎(chǔ)
2.1.1 HTTP的請求報(bào)文
- 報(bào)文結(jié)構(gòu)分為為:報(bào)文首部、空行、報(bào)文主體
- 按照是請求還是響應(yīng),報(bào)文類型分為:請求報(bào)文、響應(yīng)報(bào)文
- 請求報(bào)文和響應(yīng)報(bào)文的首部有一些不同
- 請求首部由,請求行、請求首部字段、通用首部字段、實(shí)體首部字段、其他 組成
- 請求行,請求方法和使用的HTTP協(xié)議
- 請求首部字段,請求特有的首部字段
- 通用首部字段,請求和響應(yīng)都有的首部字段
- 實(shí)體首部字段,用于描述報(bào)文主題的字段
- 其他,非協(xié)議內(nèi)但是因?yàn)槠渌猛颈谎a(bǔ)充進(jìn)來的字段
- 響應(yīng)首部由,狀態(tài)行、響應(yīng)首部字段、通用首部字段、實(shí)體首部字段、其他 組成
- 狀態(tài)行,請求的結(jié)果狀態(tài)碼和使用的HTTP協(xié)議
- 響應(yīng)首部字段,響應(yīng)特有的首部字段
- 通用首部字段,同上
- 實(shí)體首部字段,同上
- 其他,同上
- 請求首部由,請求行、請求首部字段、通用首部字段、實(shí)體首部字段、其他 組成
2.1.2 響應(yīng)報(bào)文中的狀態(tài)碼
記錄在HTTP中的規(guī)范多大40多種,最近常使用的有14中,用途的大致介紹如下:
- 2XX 表明請求被正常處理了
- 3XX 表明需要執(zhí)行某些特殊的請求才能正確處理請求
- 4XX 客戶端發(fā)生了錯(cuò)誤
- 5XX表明服務(wù)器發(fā)生了錯(cuò)誤
后文中需要特別留意的是200和304狀態(tài),分別表示請求正常返回和使用瀏覽器的緩存。
2.2 調(diào)試工具Fiddler
2.2.1 安裝和配置Fiddler
Fiddler是一款流行的抓包工具,最初只運(yùn)行在Windows上,但是現(xiàn)在也提供了在其他環(huán)境的安裝包,但是需要首先安裝Mono,基于Mono才能運(yùn)行Fiddler。如果是在Mac上,可以參考如下這篇文章對Fiddler進(jìn)行配置:
注:原文存在一些問題和說明不夠細(xì)致的地方,如下步驟有所補(bǔ)充說明
- 下載Mono
- 配置
// 下載受信任的證書
/Library/Frameworks/Mono.framework/Versions/5.10.1/bin/mozroots --import --sync
// 打開.bash_profile 文件
sudo vi ~/.bash_profile
// 增加執(zhí)行目錄
export MONO_HOME=/Library/Frameworks/Mono.framework/Versions/5.10.1
export PATH=$PATH:$MONO_HOME/bin
// 使.bash_profile應(yīng)用有效
source ~/.bash_profile
- 下載Fiddler
- 執(zhí)行
mono32 Fiddler.exe直接執(zhí)行mono會(huì)報(bào)64錯(cuò)誤注意查看mono的help說明
2.2.2 使用Fiddler
運(yùn)行Fiddler以后,可以看到所有的請求的報(bào)文信息,也可以在Fiddler下的執(zhí)行命令行中輸入形如 bpu localhost:8080的命令來調(diào)試特定的請求。比如我們訪問了 localhost:8080以后,F(xiàn)iddler會(huì)默認(rèn)在Request請求中出現(xiàn)斷點(diǎn)。
可以修改請求首部,也可以通過break on response將斷點(diǎn)停在響應(yīng)中來修改響應(yīng)首部,也可以直接break complete跳過斷點(diǎn)。
如上的使用方法可以用來對下文中提到的各種緩存首部字段進(jìn)行模擬,從而模擬出效果。
3. HTTP緩存
2.1 HTTP緩存的的分類
強(qiáng)緩存
瀏覽器在加載資源時(shí),先根據(jù)這個(gè)資源的一些首部判斷它是否命中,如果命中,瀏覽器直接從自己的緩存中讀取資源,不會(huì)發(fā)請求到服務(wù)器。
協(xié)商緩存
當(dāng)強(qiáng)緩存沒有命中的時(shí)候,瀏覽器一定會(huì)發(fā)送一個(gè)請求到服務(wù)器,通過服務(wù)器端依據(jù)資源的另外一些首部驗(yàn)證這個(gè)資源是否命中協(xié)商緩存,如果協(xié)商緩存命中,服務(wù)器會(huì)將這個(gè)請求返回,但是不會(huì)返回這個(gè)資源的數(shù)據(jù),而是告訴客戶端可以直接從緩存中加載這個(gè)資源。
一些特點(diǎn)
- 強(qiáng)緩存不發(fā)請求到服務(wù)器,協(xié)商緩存會(huì)發(fā)請求到服務(wù)器。
- 如果命中,都是從客戶端緩存中加載資源,而不是從服務(wù)器加載資源數(shù)據(jù)。
- 當(dāng)協(xié)商緩存也沒有命中的時(shí)候,瀏覽器直接從服務(wù)器加載資源數(shù)據(jù)。
2.2 強(qiáng)緩存
在Chrome瀏覽器上打開常用的站點(diǎn),打開network查看請求,可以看到請求的返回狀態(tài)是200,但是size一欄顯示的是 from disk cache 或 from memory cache的請求,這些請求的響應(yīng)就屬于命中了強(qiáng)緩存。
2.2.1 強(qiáng)緩存的實(shí)現(xiàn)
強(qiáng)緩存是利于Expires和Cache-Control這兩個(gè)通用首部中的字段來控制的。這兩個(gè)首部字段可以都使用也可以只啟用一個(gè),同時(shí)存在時(shí)Cache-Control的優(yōu)先級(jí)更高。
Expires
- 在HTTP/1.0中提出,由服務(wù)器返回用于表示資源的過期時(shí)間,描述的是絕對時(shí)間
- 在瀏覽器第一次請求資源的時(shí)候,響應(yīng)的首部中會(huì)包含Expires
- 瀏覽器時(shí)收到資源以后,把資源(連同首部)一起緩存下來
- 瀏覽器再次請求的時(shí)候,在緩存中找到該資源然后用Expires跟當(dāng)前時(shí)間比較
- 如果命中,則使用緩存中的資源(使用的首部也是之前緩存的)
- 如果沒命中,則從服務(wù)器請求資源,并且首部中的字段也會(huì)被更新
Cache-Control
- 在HTPP/1.1中提出,配置時(shí)間使用的時(shí)長是絕對量
Cache-Control: max-age=30 - 在瀏覽器第一次請求資源的時(shí)候,響應(yīng)的首部中會(huì)包含Cache-Control
- 瀏覽器時(shí)收到資源以后,把資源(連同首部)一起緩存下來
- 瀏覽器再次請求的時(shí)候,在緩存中利用首部的Cache-Control計(jì)算一個(gè)過期時(shí)間
- 如果當(dāng)前時(shí)間早于過期就命中,直接使用緩存中的資源
- 否則就從服務(wù)器中加載資源
2.2.2 強(qiáng)緩存的配置
(1)可以通過代碼來設(shè)置是否啟用強(qiáng)緩存。
java.util.Date date = new java.util.Date();
response.setDateHeader("Expires",date.getTime()+20000); // 緩存一定時(shí)間
response.setDateHeader("Expires", 0); // 不緩存
// 瀏覽器和緩存服務(wù)器都(public:可以緩存, no-cache:不緩存)
response.setHeader("Cache-Control", "public/no-cache");
(2)也可以通過Web服務(wù)器來設(shè)置是否啟用強(qiáng)緩存
多數(shù)服務(wù)器都提供了配置項(xiàng)可以對是否啟用強(qiáng)緩存進(jìn)行配置,使用時(shí)可自行查閱。
2.3 協(xié)商緩存
在Chrome的network中查看請求,發(fā)現(xiàn)不少是狀態(tài)為304狀態(tài)描述為Not Modified,這意味請求命中了協(xié)商緩存。
2.3.1 協(xié)商緩存的實(shí)現(xiàn)
當(dāng)瀏覽器請求某個(gè)資源沒有命中強(qiáng)緩存,就會(huì)發(fā)請求到服務(wù)器去驗(yàn)證協(xié)商緩存,如果命中,服務(wù)器會(huì)返回304告知瀏覽器使用協(xié)商緩存,即使用瀏覽器的緩存中加載。
協(xié)商緩存可以使用 Last Modified 和 If-Modified-Since,以及 Etag 和 If-None_match 這兩種方式來實(shí)現(xiàn)。
Last Modified 和 If-Modified-Since
- 在瀏覽器第一次請求服務(wù)器的資源時(shí),會(huì)在響應(yīng)首部中增加Last-Modified代表資源在服務(wù)器上的最后修改時(shí)間
- 瀏覽器再次請求資源時(shí),會(huì)在請求首部上加上If-Modified-Since,它的值是響應(yīng)首部返回的Last-Modified的值。
- 服務(wù)器收到了If-Modified-Since以后,會(huì)跟服務(wù)器資源的最后修改時(shí)間比對,如果發(fā)現(xiàn)沒變化,則返回304,相應(yīng)的不會(huì)再往響應(yīng)首部中增加Last-Modified
- 瀏覽器收到304后,會(huì)從瀏覽器緩存中加載資源
- 如果沒有命中協(xié)商緩存,瀏覽器從服務(wù)器加載資源,并更新響應(yīng)首部中Last-Modified的值,下次在請求資源的時(shí)候If-Modified-Since就會(huì)使用這個(gè)新值。
ETag 和 If-None-Match
- 瀏覽器第一次跟服務(wù)器請求一個(gè)資源時(shí),服務(wù)器返回資源的同時(shí),會(huì)在響應(yīng)首部中增加ETag這個(gè)屬性,這個(gè)屬性代表了服務(wù)器根據(jù)當(dāng)前請求的資源生成的唯一標(biāo)識(shí)。
- 瀏覽器再次請求這個(gè)資源時(shí),就會(huì)在請求首部中增加If-None-Match,使用的是響應(yīng)首部首部中返回的ETag的值。
- 服務(wù)器接收到資源請求時(shí),會(huì)根據(jù)傳過來的If-None-Match和資源生成的新的ETag來進(jìn)行比較,如果相同則返回304,瀏覽器收到304后,會(huì)從瀏覽器緩存中加載資源
- 如果If-None-Match新生成的ETag不同,則連同資源和新生成的ETag一起返回給瀏覽器
說明:
- ETag這個(gè)標(biāo)識(shí)是個(gè)字符串,只要資源有變化標(biāo)識(shí)就會(huì)不同,而不以修改時(shí)間為依據(jù)。
- ETag跟Last-Modified不同的地方時(shí),不管是否跟之前返回的ETag是否一致都會(huì)返回。
2.3.2 協(xié)商緩存的管理
多數(shù)Web服務(wù)器都會(huì)開啟協(xié)商緩存,并且是同時(shí)啟用 Last Modified 和 If-Modified-Since,以及 ETag 和 If-None-Match 。
分布式系統(tǒng)應(yīng)該保證多臺(tái)機(jī)器的文件Last-Modified一致,應(yīng)該盡量關(guān)閉掉ETag(因?yàn)槊颗_(tái)機(jī)器生成的ETag都不一樣),否則會(huì)導(dǎo)致對比失敗。
協(xié)商緩存必須配合強(qiáng)緩存一起使用,因?yàn)椴辉O(shè)置強(qiáng)緩存,意味著協(xié)商緩存每次都要請求服務(wù)器資源會(huì)加大服務(wù)器的負(fù)荷。
2.4 緩存的實(shí)踐
實(shí)踐中的積累太少,需要在開發(fā)中不斷積累實(shí)例,待補(bǔ)充...
3 應(yīng)用層(瀏覽器)緩存
3.1 應(yīng)用層緩存介紹
3.1.1 manifest
manifest離線緩存技術(shù)是為了在網(wǎng)絡(luò)連接斷開時(shí)候也能正常瀏覽網(wǎng)站,因?yàn)樵O(shè)計(jì)實(shí)時(shí)的數(shù)據(jù)請求部分無法緩存,因此該技術(shù)的適用場景有限,即只適用于哪些內(nèi)容相對固定的頁面。
實(shí)現(xiàn)上分為客戶端和服務(wù)器端兩個(gè)配置:
- 客戶端,需要在頁面通過形如
<meta manifest="index.appcache">來指定緩存內(nèi)容 - 服務(wù)器,配置
index.appcache文件,里面指定了哪些內(nèi)容可/不可緩存
3.1.2 Storage, Database, Cache
Storage: localStorage, sessionStorage
兩者都是比較新的API,在每個(gè)瀏覽器上的具體使用可能會(huì)有不同,這里以Chrome為例。除了可以通過在Application中的對應(yīng)欄中編輯Storage以外,還可以通過代碼來添加和刪除屬性對。
localStorage 和 sessionStorage的區(qū)別礙于作用方位,localStorage會(huì)存在瀏覽器端跨Tab通用,并且不會(huì)隨著瀏覽器關(guān)閉而丟失;sessionStorage則只在當(dāng)前的瀏覽器Tab中有效,關(guān)閉以后就丟失了(但是刷新以后仍舊能保持)。
Database: IndexDB, Web SQL
IndexDB是一個(gè)客戶端的用于存儲(chǔ)結(jié)構(gòu)化數(shù)據(jù)的API,它所涉及的概念跟關(guān)系型數(shù)據(jù)庫中非常類似,可以使用前端代碼來方便地進(jìn)行仿數(shù)據(jù)庫方式的數(shù)據(jù)管理。可以參考 working-with-indexdb進(jìn)一步了解IndexDB.
Cache: Service Worker Caches, Application Cache
待補(bǔ)充...
3.2 應(yīng)用層緩存實(shí)踐
待補(bǔ)充...