什么是緩存
緩存 是應用程序中很重要的一個概念,在有大量數(shù)據(jù)交換的應用程序中,我們會采取一些方式將那些實時性要求不高的數(shù)據(jù)生成副本并存儲在某個相對來說可快速到達、訪問、獲取的倉庫,這樣在需要這些數(shù)據(jù)的時候我們直接從這個倉庫中獲取數(shù)據(jù)。
緩存的目的主要有兩點:
- 提升數(shù)據(jù)交換的性能(速度)
- 緩解服務器或數(shù)據(jù)庫的壓力
http緩存概述
當用戶開始訪問一個網(wǎng)站時,瀏覽器會從目標服務器獲取一些資源用以構建最終的web頁面,比如css、js、html等靜態(tài)文件。假設我們不采取任何措施,則用戶每次訪問這個網(wǎng)站都要發(fā)起一系列http請求,試想,如果這個網(wǎng)站的pv達到上百萬甚至上千萬,會對網(wǎng)站的后臺服務器造成多大的壓力。為了盡可能提升網(wǎng)站的性能,http協(xié)議給出了一個優(yōu)化方案:

上圖是當用戶第一次請求一個資源時的時序圖,瀏覽器會先詢問是否有命中緩存(第一次請求肯定是沒緩存啦),沒有命中的緩存則瀏覽器再從服務器獲取資源并將資源放進緩存?zhèn)}庫中,下次則可以從緩存中拿資源了。為方便理解,我們認為瀏覽器提供了緩存數(shù)據(jù)庫,只要瀏覽器發(fā)現(xiàn)滿足了某些緩存規(guī)則,就可以直接從緩存數(shù)據(jù)庫中取出你需要的資源。
上述是一個簡單過程,但是事實上的緩存策略還要更復雜一點。簡單來說,http根據(jù)是否要向服務器發(fā)送請求將緩存規(guī)則分為了兩類:強緩存和對比緩存(對比緩存也叫做協(xié)商緩存)。
強緩存
強緩存直接從緩存數(shù)據(jù)庫中取出資源,無需再發(fā)送請求到服務器上:

http中用來判斷是否命中強緩存的字段為Expires和Cache-Control,Cache-Control優(yōu)先級高于Expires。
1. Expires
注:expires字段是HTTP 1.0 時代的產(chǎn)物,現(xiàn)在的瀏覽器用的全都是HTTP 1.1了,所以這個字段的作用基本可以忽略 。
來看下某個網(wǎng)站的一次請求中的信息:

expires的值是一個絕對時間,可以看到上圖中的時間點:2019年5月30號08:04:42,這代表:這個資源在這個時間點之前都可以直接從緩存中獲取。
2. Cache-Control
仍舊是上面請求中響應頭信息:

cache-control中定義了 public 和 max-age=7200,這是一個相對時間(單位:秒),這里代表資源的緩存在這個請求之后的2小時內(nèi)都有效。
請求頭cache-control字段列表:
- Cache-Control: max-age=<seconds>
- Cache-Control: max-stale[=<seconds>]
- Cache-Control: min-fresh=<seconds>
- Cache-control: no-cache
- Cache-control: no-store
- Cache-control: no-transform
- Cache-control: only-if-cached
響應頭cache-control字段列表:
- Cache-control: must-revalidate
- Cache-control: no-cache
- Cache-control: no-store
- Cache-control: no-transform
- Cache-control: public
- Cache-control: private
- Cache-control: proxy-revalidate
- Cache-control: max-age=<seconds>
- Cache-control: s-maxage=<seconds>
cache-control常見字段的含義:
- public
表明響應可以被任何對象(包括:發(fā)送請求的客戶端,CDN等代理服務器,等等)緩存,即使是通常不可緩存的內(nèi)容(例如,該響應沒有max-age指令或Expires消息頭)。 - private
表明響應只能被單個用戶緩存,不能作為共享緩存(即代理服務器不能緩存它)。私有緩存可以緩存響應內(nèi)容。 - no-cache
可以在本地進行緩存,但每次發(fā)請求時,都要向服務器進行驗證,如果服務器允許,才能使用本地緩存(即:需要協(xié)商緩存)。 - no-store
禁止緩存客戶端請求或服務器響應的內(nèi)容,每次都須重新請求服務器拿內(nèi)容 - max-age
設置緩存存儲的最大周期,超過這個時間緩存被視為過期 (單位**:秒)
更詳細的cache-control字段含義請看 MDN
強緩存狀態(tài)碼
強緩存狀態(tài)碼為200,但查看chrome的network會發(fā)現(xiàn)狀態(tài)碼后面多了個注釋:

事實上,強緩存時,這個注釋會有兩種情況:
- from memory cache
- from disk cache
1. from memory cache:
緩存資源在內(nèi)存中,瀏覽器(或頁面標簽)關閉后內(nèi)存中的緩存就會被釋放,重新打開頁面取不到該緩存。
2. from disk cache
緩存資源在硬盤中,瀏覽器(或頁面標簽)關閉后硬盤中的緩存不會消失,下次進入頁面還能從硬盤中獲取。
通常的緩存策略,瀏覽器打開一個網(wǎng)頁,如果該網(wǎng)頁最近訪問過,那么資源可能會出現(xiàn)from disk cache,從硬盤中讀取緩存;:

如果此時刷新頁面,該資源會出現(xiàn)from memory cache,從內(nèi)存中讀取緩存。眾所周知,內(nèi)存永遠是最快的。
如果不想從強緩存中獲取資源,windows電腦可以通過
ctrl + f5刷新頁面,mac os 可以通過shift + command + r刷新頁面,刷新后你可以看到資源不會出現(xiàn) from disk(or memory) cache了。
對比緩存
對比緩存是需要經(jīng)過服務器確認是否使用緩存的機制,其http狀態(tài)碼為304,意為not modified。其過程如下:

可以看到,雖然客戶端仍然發(fā)起了http請求服務器,但是服務器只做了標志對比來確認是否使用緩存,如果確認使用緩存,就不會再返回具體的資源了。這樣做雖然沒有減少請求數(shù)量,但是極大減小了請求負荷,可以明顯提升請求速度和減小網(wǎng)絡帶寬。
問題是,如何對比標志來確認是否使用緩存?這里主要涉及到兩種標志:
- Last-Modified / If-Modified-Since
- Etag / If-None-Match
1. Last-Modified / If-Modified-Since
當瀏覽器第一次訪問一個資源的時候,服務器會在response header中返回一個Last-Modified,代表這個資源最后的修改時間,當瀏覽器再次訪問這個資源的時候,會在request header中帶上 If-Modified-Since,值為上次請求時服務器返回的 Last-Modified 的值,然后服務器根據(jù)資源上次修改的時間確認資源在這段期間內(nèi)是否更改過,如果沒有,則返回304,如果有,則返回200并返回最新的資源。

如上圖,客戶端給服務器的 If-Modified-Since 值和服務端給的Last-Modified的值相同,表示2018年6月21號02:48:50至今,這個資源都沒被修改過,所以瀏覽器可以從緩存中獲取。再看其請求負荷,為575B:

通過shift+command+r刷新看下其真實大小,為4.3KB:

2. Etag / If-None-Match
Etag / If-None-Match 與 Last-Modified / If-Modified-Since 的機制類似,不同的是,Etag是通過一個校驗碼來對比資源是否更改過的,而不是通過資源的修改時間。當一個資源修改時,其校驗碼也會更改。當瀏覽器請求資源時,服務器會返回一個Etag字段,然后瀏覽器下一次請求時,會帶上 If-None-Match ,值為上次服務器返回的Etag的值,服務器經(jīng)過校驗碼的對比后決定返回200或304。
看個例子:

上圖中request header中帶上了 If-None-Match,值為 5b506e03-25856,response header中返回了Etag,值也是 5b506e03-25856,證明文件沒有修改過可以從緩存中獲取。
你可能注意到 If-None-Match 的值中有個 W/ 前綴,這個其實不用去關心,這個是用來提示應該采用弱比較算法(其實是畫蛇添足,因為 If-None-Match 用且僅用這一算法)。
Etag和Last-Modified優(yōu)先級
Etag可以解決 Last-Modified 不太好處理的問題,Etag能更準確地控制緩存,因此,如果http請求中若同時出現(xiàn)Etag和Last-Modified,Etag的優(yōu)先級是高于 Last-Modified 的。具體地說,Last-Modified 有以下一些問題:
- 一些文件也許會周期性的更改,但是他的內(nèi)容并不改變(僅僅改變的修改時間),這個時候我們并不希望客戶端認為這個文件被修改了,而重新GET;
- 某些文件修改非常頻繁,比如在秒以下的時間內(nèi)進行修改,(比方說1s內(nèi)修改了N次),If-Modified-Since能檢查到的粒度是s級的,這種修改無法判斷(或者說UNIX記錄MTIME只能精確到秒);
- 某些服務器不能精確的得到文件的最后修改時間。
總結
網(wǎng)上有副HTTP緩存邏輯流程圖,可以很清楚地表明其緩存策略:
