前端知必會的緩存規則

前言

緩存是一種保存資源副本並在下次請求時直接使用該副本的技術。

這是緩存的官方定義。緩存從來都是前端的一個痛點,緩存與瀏覽器版本和 HTTP 版本也有不少關係,本文利用Firefox 59.0.2(64位)進行演示,因為該瀏覽器可以最大程度契合HTTP緩存的表現,當然文中也會涉及Chrome 65.0.3325.181 (正式版本) (64 位)和Safari等瀏覽器的特殊性。

正文

緩存的好處

所謂緩存目的就是加速各種靜態資源的訪問。

  • 減輕服務器端的訪問壓力(不用每次去請求資源)
  • 加快頁面的載入速度,提升用戶體驗(打開本地資源速度當然比請求回來再打開要快得多)
  • 減少帶寬消耗

✨緩存的種類

很多的開發者把cookie、localStorage、sessionStorage以及indexDB存儲的數據也稱之為緩存,理由是它們都是保存在客戶端的數據,其實這是不嚴謹的,cookie的存在更多的是為了讓服務端區別用戶,localStorage、sessionStorage以及indexDB則是為了保存大量結構化數據,當然也可以用來緩存js和css等靜態資源

宏觀

從宏觀上來看,緩存可分為私有緩存和共享緩存。

類型定義值說明私有緩存各級代理不能緩存的緩存,僅瀏覽器設置緩存Cache-Contro:private共享緩存能被各級代理緩存的緩存,表示瀏覽器和代理服務器都可以設置緩存Cache-Contro:public

上述表提到了瀏覽器緩存、服務器緩存以及Cache-Control 在下文中都會有更加具體地介紹

微觀

從微觀角度,緩存可以分為:

  • 瀏覽器緩存 瀏覽器緩存相信大家都知道,就是存儲在你本地磁盤上資源副本,當用戶點擊前進或者後退按鈕或是再次去訪問某個頁面的時候能夠更快的響應
  • 代理服務器緩存

代理服務器緩存原理和瀏覽器端類似,但規模要大得多,它可以為多個用戶提供緩存,其實就是共享緩存,因為同一個緩存可能會被重用多次

  • 反向代理服務器緩存

反向代理服務器緩存也叫做網關緩存(nginx反向代理、CDN緩存),網關也是一箇中間服務器。

一段反向代理服務器緩存的配置:

location / {
 location ~ \.(php|php5)?$ {
 proxy_pass http://www.gao.com;
 proxy_set_header X-RealA-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_set_header Host $Host;
 proxy_redirect off;
 }
 location ~ .*\.(html|htm|gif|jpg|jpeg|bmp|png|ico|txt|js|css)$ {
 root /data/webapps/gao; //靜態類資源由本地響應
 expires 3d;
 add_header Static Nginx-Proxy;
 }
 }

說明:代理服務器緩存和反向代理服務器緩存的區別

答:代理服務器存在的目的是用來減輕客戶端網絡帶寬流,具體來說:假設公司架設了一臺代理服務器,請求通過代理服務器後,代理服務器將資源緩存下來,供其他用戶訪問,這樣一來,用戶發起的請求無需再次穿越Internet,從而降低網絡流量,節省資費。(類似瀏覽器緩存,返回200 from dist cache或者200 from memory cache) (這裡有些疑問,不確定來自本地和代理服務器緩存,更新一下,現在已經可以確定,來自代理服務器的緩存,請求仍然會是200,無論是強緩存還是協商緩存都是對於瀏覽器來說的,如果是200,可能來源於服務端緩存或者真實服務端)

前端知必會的緩存規則

反向代理服務器存在的目的是用來減輕源服務器的負載,而不是用來減輕客戶端網絡帶寬流量,具體來說如果有緩存會返回304狀態碼,如下圖:

前端知必會的緩存規則

補充說明:網上關於200 from dist cache或者200 from memory cache存在兩種說法:

Chrome會根據本地內存的使用率來決定緩存存放在哪,如果磁盤使用率很高,放在磁盤裡面,內存的使用率很高會暫時放在內存裡面


當首次訪問網頁時,資源文件被緩存在內存中,同時也會在本地磁盤中保留一份副本。當用戶刷新頁面,如果緩存的資源沒有過期,那麼直接從內存中讀取並加載。當用戶關閉頁面後,當前頁面緩存在內存中的資源被清空。當用戶再一次訪問頁面時,如果資源文件的緩存沒有過期,那麼將從本地磁盤進行加載並再次緩存到內存之中。

經過測試認為第一種說法更加合理(Chrome驗證):

前端知必會的緩存規則

上述兩張圖來自於百度首頁,一個來自於dist cache,一個來自memory cache,兩張圖的request headers不同是區別兩種緩存的方式

  • 數據庫緩存

將查詢後的數據放到內存中,下一次查詢直接從內存中讀取

HTTP中和緩存相關的首部字段

1. 通用首部字段

字段名稱說明Cache-Control控制緩存具體的行為PragmaHTTP1.0時的遺留字段,當值為"no-cache"時強制驗證緩存Date創建報文的日期時間(啟發式緩存階段會用到這個字段)

2. 響應首部字段

字段名稱說明ETag服務器生成資源的唯一標識Vary代理服務器緩存的管理信息Age資源在緩存代理中存貯的時長(取決於max-age和s-maxage的大小)

3. 請求首部字段

字段名稱說明If-Match攜帶上一次請求中資源的ETag,比較文件是否有新的修改If-None-Match攜帶上一次請求中資源的ETag,比較文件是否沒有新的修改If-Modified-Since攜帶上一次請求中資源的Last-Modified,比較資源前後兩次訪問最後的修改時間是否一致If-Unmodified-Since攜帶上一次請求中資源的Last-Modified,比較資源前後兩次訪問最後的修改時間是否不一致

4. 實體首部字段

字段名稱說明ExpiresHTTP1.0時的遺留字段,實體主體的過期時間Last-Modified資源最後一次修改的時間

首部字段的取值詳解

前端知必會的緩存規則

圖來自《圖解HTTP》一書,再根據上述的描述我們知道,請求首部和響應首部的Cache-Control都與緩存有關,一個是從客戶端角度,一個是從服務端角度

1. Cache-Control

緩存請求指令

指令參數說明no-cache會進行緩存,但是需要強制源服務器再次驗證no-store不緩存請求或是響應的任何內容max-age=單位是秒緩存的時長,也是響應的最大的Age值,當max-age=0時,效果等同於no-cachemin-fresh=在指定時間內,響應仍然有效no-transform代理不可更改媒體類型only-if-cached從緩存獲取cache-extension新的指令標記(token)

緩存響應指令

指令參數說明public任意一方都能緩存該資源(客戶端、代理服務器等)private只能特定用戶緩存該資源no-cache緩存前必須先確認其有效性no-store不緩存請求或響應的任何內容max-age=單位是秒緩存的時長,也是響應的最大的Age值,當max-age=0時,效果等同於no-caches-maxage=公共緩存服務器響應的最大Age值must-revalidate可緩存但必須再向源服務器進確認proxy-revalidate要求中間緩存服務器對緩存的響應有效性再進行確認no-transform代理不可更改媒體類型cache-extension新的指令標記(token)

public和private指令

這兩個指令只在響應指令中出現,public表示所有的代理服務器和客戶端均可緩存響應,假設我們都走的公司代理服務器,公司可以緩存一些資源,我們就可以都訪問到該緩存,而private只針對特定用戶緩存

no-cache指令

前端知必會的緩存規則

2. Expires

如同時存在max-age 和 Expires ,當應用為HTTP/1.1版本時,會忽略Expires首部字段,當HTTP/1.0版本時,會忽略max-age

3. Pragma

這是HTTP/1.0裡面的一個字段,優先級高於Cache-Control和Expires一般會這麼使用

但是事實上這種禁用緩存的形式用處很有限:

  1. 僅有IE才能識別這段meta標籤含義,其它主流瀏覽器僅能識別Cache-Control: no-store的meta標籤。
  2. 在IE中識別到該meta標籤含義,並不一定會在請求字段加上Pragma,但的確會讓當前頁面每次都發新請求

上述提到的都是瀏覽進行緩存表現的字段,下面說一說校驗字段

4. Last-Modified和If-Modified-Since

服務器可以通過Last-Modified響應首部來指明當前訪問資源最近一次修改是在什麼時候,客戶端在發送請求校驗資源是否發生修改時,可以通過If-Modified-Since請求首部向服務器端確認資源在給定時間點之後是否發生更改,服務器可以根據比對資源的最近一次修改時間和If-Modified-Since首部指定的時間來決定返回304狀態碼還是返回新的資源。

5. ETag和If-Match

使用上述機制存在的問題就是:

  1. 因為Last-Modified指定的修改時間是精確到秒級的,如果服務器上的文件在一秒內發生多次更改,單純依靠文件修改時間就檢測不到文件已經發生變化
  2. 如果文件在某個時間段內發生多次更改,但是前後文件內容並沒有發生變化,這時依靠文件修改時間去判斷文件已經發生修改也不合適

因此產生了ETag和If-Match。

通過Etag響應首部就能夠解決上面提到的問題。通過對當前文件內容計算生成一個唯一標識,通過比對標識是否相同來判斷文件內容是否發生變化。相比於比較文件修改時間,這種方式更加有效。客戶端在發送條件請求時,會包含 If-None-Match 請求首部,來判斷文件內容是否發生變化。

6. Date

最後介紹一個響應頭中的Date字段,該字段是區分緩存來自源服務器還是緩存服務器,如果多次刷新Date一直變化,說明緩存來自源服務器,否則來自緩存服務器

️瀏覽器的緩存策略

瀏覽器對於緩存的處理是根據第一次請求資源時返回的響應頭來確定的。 在前一部分中,介紹了關於緩存的主要字段,而這些字段與瀏覽器的緩存策略息息相關。

強緩存階段

強緩存主要是採用響應頭中的Cache-Control和Expires兩個字段進行控制的,如同時存在max-age 和 Expires ,當應用為HTTP/1.1版本時,會忽略Expires首部字段,當HTTP/1.0版本時,會忽略max-age,由於Expires是HTTP1.0時代的產物,因此設計之初就存在著一些缺陷,如果本地時間和服務器時間相差太大,就會導致緩存錯亂。

客戶端通過對比本地時間和服務器生存時間來檢測緩存是否可用,如果可用,客戶端會直接使用緩存,如果不可用,會進入協商緩存階段。

協商緩存階段

強緩存機制如果檢測到緩存失效,就需要進行服務器再驗證。這種緩存機制也稱作協商緩存。

瀏覽器在第一次獲取請求的時候,就會在響應頭中攜帶上資源的上次服務器修改日期(Last-Modified)或者資源的標籤(Etag)。後續的請求服務器會根據請求頭上的If-Modified-Since(對應Last-Modified)和(If-None-Match)字段來判斷資源是否失效,一旦資源過期,則服務器會重新發送新的資源到客戶端上,從而保證資源的有效性。

需要注意的是當響應頭中同時存在Etag和Last-Modified的時候,會先對Etag進行比對,隨後才是Last-Modified。

啟發式緩存階段

有一種特殊的響應頭類似這樣

Age:23146
Cache-Control: public
Date:Tue, 28 Nov 2017 12:26:41 GMT
Last-Modified:Tue, 28 Nov 2017 05:14:02 GMT
Vary:Accept-Encoding

如果沒有Expires或max-age確定緩存過期時間的字段,瀏覽器會默認根據響應頭中2個時間字段 Date 和 Last-Modified 之間的時間差值,取其值的10%作為緩存時間週期 這就是啟發式緩存階段

用戶行為與緩存

操作說明打開新窗口如果指定cache-control的值為private、no-cache、must-revalidate,那麼打開新窗口訪問時都會重新訪問服務器。而如果指定了max-age值,那麼在此值內的時間裡就不會重新訪問服務器,例如:Cache-control: max-age=5 表示當訪問此網頁後的5秒內不會去再次訪問服務器.在地址欄回車如果值為private或must-revalidate,則只有第一次訪問時會訪問服務器,以後就不再訪問。如果值為no-cache,那麼每次都會訪問。如果值為max-age,則在過期之前不會重複訪問。按後退按扭如果值為private、must-revalidate、max-age,則不會重訪問,而如果為no-cache,則每次都重複訪問.按刷新按扭無論為何值,都會重複訪問.(可能返回狀態碼:200、304,這個不同瀏覽器處理是不一樣的,FireFox正常,Chrome則會啟用緩存(200 from cache))按強制刷新按鈕重新請求

瀏覽器行為

對比一:

上述說明過,Firefox 59.0.2(64位)下的http緩存行為正常

前端知必會的緩存規則

如上圖,刷新頁面,瀏覽器會帶上Cache-Control: max-age=0,因此返回304。

前端知必會的緩存規則

但是Chrome 65.0.3325.181 (正式版本) (64 位)的刷新卻不會帶上Cache-Control: max-age=0的請求頭,打開百度會發現無論如何點擊刷新按鈕,依然返回的是200。

另外這個奇怪的情況在不同的網站甚至不同的電腦下出現頻率都不一致,所以暫時將其歸咎於瀏覽器的怪異反應。

那麼有這麼一個問題——是否有辦法在瀏覽器點擊“刷新”按鈕的時候不讓瀏覽器去發新的驗證請求呢?

$(window).load(function() {
 var bg='http://img.infinitynewtab.com/wallpaper/100.jpg';
 setTimeout(function() { //setTimeout是必須的
 $('#bgOut').css('background-image', 'url('+bg+')');
 },0);
});

來源知乎

對比二:

在這裡服務端的max-age=0,新建標籤頁時Firefox的表現:

前端知必會的緩存規則

新建標籤頁時Chrome的表現:

前端知必會的緩存規則

經測試發現,Chrome沒有按照預期返回304狀態碼,而且Chrome有的時候返回200 from disk cache有時返回200,所以暫時將其歸咎於瀏覽器的怪異反應。

怪異現象的思考

由於chrome的現象,便在網上查找chrome緩存資料

下述一段回答來自stackoverflow,

Chrome's cache is a database, and that database is also compressed to save space. When you retrieve a document from cache, the price of that retrieval is not zero. Chrome has to look up the item in the cache database, and then inflate that entry into memory so Chrome can work with it. I don't know the exact details concerning how the Network chrome-dev-tools panel shows the times, but I would guess that getting that file from disk, uncompressing it, and then parsing and working with the result is what you're seeing reflected in "Time downloaded."

大致的意思是:谷歌瀏覽器的緩存是一個數據庫,它每次從緩存中加載數據時,實際是在數據庫中找到索引,如果發現有變化,將其下載出來,但是對外展示200 from disk cache

一個小demo

測試cache-control

max-age=0時:

後端代碼:

'use strict';
var express = require('express'),
	app = express();
app.use(express.static("./", {
	maxAge: 0
}));
app.listen(4000);

max-age=0前端代碼:



	
	 


	

 

看看Firefox 59.0.2(64位)下的http緩存行為

前端知必會的緩存規則

前端知必會的緩存規則

看看Chrome 65.0.3325.181 (正式版本) (64 位)下的http緩存行為

前端知必會的緩存規則

前端知必會的緩存規則

新建標籤頁的行為更加詭異,會直接返回200 from disk cache 但是我如果修改了部分代碼,即使返回200 from disk cache也會展示出來更新,這是瀏覽器內部機制,沒有詳細瞭解,感興趣可以自己查閱資料

max-age=10時:

  1. Firefox刷新會帶上Cache-Control:max-age=0進行協商緩存校驗,回車或者新建標籤頁在10s內會返回200 from cache,10s之後會進行協商緩存校驗
  2. Chrome刷新或者回車在10s內返回200 from cache,10s之後會進行協商緩存校驗,新建標籤頁則會返回200 from disk cache

總結:

  1. 當強緩存失效時,一定需要帶上協商緩存的字段進行校驗
  2. 如果瀏覽器請求頭中攜帶Cache-control:max-age=0,那麼瀏覽器會帶上協商緩存的字段進行校驗

實際使用

考慮緩存的內容:

  • css樣式文件
  • js文件
  • logo、圖標
  • html文件
  • 可以下載的內容

不經常改變的文件:

給 max-age 設置一個較大的值,一般設置 max-age=31536000

可能經常需要變動的文件:

Cache-Control: no-cache / max-age=0 比如入口 index.html 文件、文件內容改變但名稱不變的資源。選擇 ETag 或 Last-Modified 來做驗證,進行協商緩存校驗


分享到:


相關文章: