1. 程式人生 > 其它 >瀏覽器快取機制剖析

瀏覽器快取機制剖析

“快取一直是前端優化的主戰場,利用好快取就成功了一半。本篇從HTTP請求和響應的頭域入手,讓你對瀏覽器快取有個整體的概念。最終你會發現強快取,協商快取 和 啟發式快取是如此的簡單。 ”

導讀

瀏覽器對於請求資源,擁有一系列成熟的快取策略。按照發生的時間順序分別為儲存策略過期策略協商策略,其中儲存策略在收到響應後應用,過期策略協商策略在傳送請求前應用。流程圖如下所示:

廢話不多說,我們先來看兩張表格。

1. HTTP Header中與快取有關的Key:

key

描述

儲存策略

過期策略

協商策略

Cache-Control

指定快取機制,覆蓋其它設定

✔️

✔️

Pragma

http1.0欄位,指定快取機制

✔️

Expires

http1.0欄位,指定快取的過期時間

✔️

Last-Modified

資源最後一次的修改時間

✔️

ETag

唯一標識請求資源的字串

✔️

2. 快取協商策略用於重新驗證快取資源是否有效,有關的Key如下:

key

描述

If-Modified-Since

快取校驗欄位,值為資源最後一次的修改時間,即上次收到的Last-Modified值

If-Unmodified-Since

同上,處理方式與之相反

If-Match

快取校驗欄位,值為唯一標識請求資源的字串,即上次收到的ETag值

If-None-Match

同上,處理方式與之相反

各個頭域(key)的作用

Cache-Control

瀏覽器快取裡,Cache-Control是金字塔頂尖的規則,它藐視一切其他設定,只要其他設定與其牴觸,一律覆蓋之。

不僅如此,它還是一個複合規則,包含多種值,橫跨儲存策略過期策略兩種,同時在請求頭和響應頭都可設定。

語法為: “Cache-Control : cache-directive”

Cache-directive共有如下12種(其中請求中指令7種,響應中指令9種):

Cache-directive

描述

儲存策略

過期策略

請求欄位

響應欄位

public

資源將被客戶端和代理伺服器快取

✔️

✔️

private

資源僅被客戶端快取,代理伺服器不快取

✔️

✔️

no-store

請求和響應都不快取

✔️

✔️

✔️

no-cache

相當於max-age:0,must-revalidate即資源被快取,但是快取立刻過期,同時下次訪問時強制驗證資源有效性

✔️

✔️

✔️

✔️

max-age

快取資源,但是在指定時間(單位為秒)後快取過期

✔️

✔️

✔️

✔️

s-maxage

同上,依賴public設定,覆蓋max-age,且只在代理伺服器上有效。

✔️

✔️

✔️

max-stale

指定時間內,即使快取過時,資源依然有效

✔️

✔️

min-fresh

快取的資源至少要保持指定時間的新鮮期

✔️

✔️

must-revalidation / proxy-revalidation

如果快取失效,強制重新向伺服器(或代理)發起驗證(因為max-stale等欄位可能改變快取的失效時間)

✔️

✔️

only-if-cached

僅僅返回已經快取的資源,不訪問網路,若無快取則返回504

✔️

no-transform

強制要求代理伺服器不要對資源進行轉換,禁止代理伺服器對 Content-Encoding,Content-Range,Content-Type欄位的修改(因此代理的gzip壓縮將不被允許)

✔️

✔️

假設所請求資源於4月5日快取,且在4月12日過期。

當max-age 與 max-stale 和 min-fresh 同時使用時,它們的設定相互之間獨立生效,最為保守的快取策略總是有效。這意味著,如果max-age=10 days,max-stale=2 days,min-fresh=3 days,那麼:

  • 根據max-age的設定,覆蓋原快取週期, 快取資源將在4月15日失效(5+10=15);
  • 根據max-stale的設定,快取過期後兩天依然有效,此時響應將返回110(Response is stale)狀態碼,快取資源將在4月14日失效(12+2=14);
  • 根據min-fresh的設定,至少要留有3天的新鮮期,快取資源將在4月9日失效(12-3=9);

由於客戶端總是採用最保守的快取策略,因此,4月9日後,對於該資源的請求將重新向伺服器發起驗證。

Pragma

HTTP1.0欄位,通常設定為Pragma:no-cache,作用同Cache-Control:no-cache。當一個no-cache請求傳送給一個不遵循HTTP/1.1的伺服器時,客戶端應該包含pragma指令。為此,勾選☑️ 上disable cache時,瀏覽器自動帶上了pragma欄位。如下:

Expires

Expires:Wed, 05 Apr 2017 00:55:35 GMT

即到期時間,以伺服器時間為參考系,其優先順序比 Cache-Control:max-age 低,兩者同時出現在響應頭時,Expires將被後者覆蓋。如果Expires,Cache-Control: max-age,或 Cache-Control:s-maxage 都沒有在響應頭中出現,並且也沒有其它快取的設定,那麼瀏覽器預設會採用一個啟發式的演算法,通常會取響應頭的Date_value - Last-Modified_value值的10%作為快取時間。

如下資源便採取了啟發式快取演算法。

其快取時間為 `(Date_value - Last-Modified_value) * 10%,計算如下:

可見該資源將於2017年4月18日23點25分41秒過期,嘗試以下兩步進行驗證:

1) 試著把本地時間修改為2017年4月18日23點25分40秒,迅速重新整理頁面,發現強快取依然有效(依舊是200 OK (from disk cache));

2) 然後又修改本地時間為2017年4月18日23點26分40秒(即往後撥1分鐘),重新整理頁面,發現快取已過期,此時瀏覽器重新向伺服器發起了驗證,且命中了304協商快取,如下所示:

3) 將本地時間恢復正常(即 2017-04-06 09:54:19)。重新整理頁面,發現Date依然是4月18日,如下所示:

從⚠️ Provisional headers are shown 和Date欄位可以看出來,瀏覽器並未發出請求,快取依然有效,只不過此時Status Code顯示為200 OK。(甚至我還專門打開了charles,也沒有發現該資源的任何請求,可見這個200 OK多少有些誤導人的意味)

可見,啟發式快取演算法採用的快取時間可長可短,因此對於常規資源,建議明確設定快取時間(如指定max-age 或 expires)。

ETag

ETag:"fcb82312d92970bdf0d18a4eca08ebc7efede4fe"

實體標籤,伺服器資源的唯一識別符號,瀏覽器可以根據ETag值快取資料,節省頻寬。如果資源已經改變,etag可以幫助防止同步更新資源的相互覆蓋。ETag 優先順序比 Last-Modified 高。

If-Match

語法:If-Match: ETag_value 或者 If-Match: ETag_value, ETag_value, …

快取校驗欄位,其值為上次收到的一個或多個etag 值。常用於判斷條件是否滿足,如下兩種場景:

  • 對於 GET 或 HEAD 請求,結合 Range 頭欄位,它可以保證新範圍的請求和前一個來自相同的源,如果不匹配,伺服器將返回一個416(Range Not Satisfiable)狀態碼的響應。
  • 對於 PUT 或者其他不安全的請求,If-Match 可用於阻止錯誤的更新操作,如果不匹配,伺服器將返回一個412(Precondition Failed)狀態碼的響應。

If-None-Match

語法:If-None-Match: ETag_value 或者 If-None-Match: ETag_value, ETag_value, …

快取校驗欄位,結合ETag欄位,常用於判斷快取資源是否有效,優先順序比If-Modified-Since高。

  • 對於 GET 或 HEAD 請求,如果其etags列表均不匹配,伺服器將返回200狀態碼的響應,反之,將返回304(Not Modified)狀態碼的響應。無論是200還是304響應,都至少返回 Cache-Control,Content-Location,Date,ETag,Expires,and Vary 中之一的欄位。
  • 對於其他更新伺服器資源的請求,如果其etags列表匹配,伺服器將執行更新,反之,將返回412(Precondition Failed)狀態碼的響應。

Last-Modified

語法:Last-Modified: 星期,日期 月份 年份 時:分:秒 GMT

用於標記請求資源的最後一次修改時間,格式為GMT(格林尼治標準時間)。如可用 new Date().toGMTString()獲取當前GMT時間。Last-Modified 是 ETag 的fallback機制,優先順序比 ETag 低,且只能精確到秒,因此不太適合短時間內頻繁改動的資源。不僅如此,伺服器端的靜態資源,通常需要編譯打包,可能出現資源內容沒有改變,而Last-Modified卻改變的情況。

If-Modified-Since

語法同上,如:

快取校驗欄位,其值為上次響應頭的Last-Modified值,若與請求資源當前的Last-Modified值相同,那麼將返回304狀態碼的響應,反之,將返回200狀態碼響應。

If-Unmodified-Since

快取校驗欄位,語法同上。表示資源未修改則正常執行更新,否則返回412(Precondition Failed)狀態碼的響應。常用於如下兩種場景:

  • 不安全的請求,比如說使用post請求更新wiki文件,文件未修改時才執行更新。
  • 與 If-Range 欄位同時使用時,可以用來保證新的片段請求來自一個未修改的文件。

強快取

一旦資源命中強快取,瀏覽器便不會向伺服器傳送請求,而是直接讀取快取。Chrome下的現象是 200 OK (from disk cache) 或者 200 OK (from memory cache)。如下:

對於常規請求,只要存在該資源的快取,且Cache-Control:max-age 或者expires沒有過期,那麼就能命中強快取。

協商快取

快取過期後,繼續請求該資源,對於現代瀏覽器,擁有如下兩種做法:

  • 根據上次響應中的ETag_value,自動往request header中新增If-None-Match欄位。伺服器收到請求後,拿If-None-Match欄位的值與資源的ETag值進行比較,若相同,則命中協商快取,返回304響應。
  • 根據上次響應中的Last-Modified_value,自動往request header中新增If-Modified-Since欄位。伺服器收到請求後,拿If-Modified-Since欄位的值與資源的Last-Modified值進行比較,若相同,則命中協商快取,返回304響應。

以上,ETag優先順序比Last-Modified高,同時存在時,前者覆蓋後者。下面通過例項來理解下強快取和協商快取。

如下忽略首次訪問,第二次通過 If-Modified-Since 命中了304協商快取。

協商快取的響應結果,不僅驗證了資源的有效性,同時還更新了瀏覽器快取。主要更新內容如下:

Age:0 表示命中了代理伺服器的快取,age值為0表示代理伺服器剛剛重新整理了一次快取。

Cache-Control:max-age=600 覆蓋 Expires 欄位,表示從Date_value,即 Wed, 05 Apr 2017 13:09:36 GMT 起,10分鐘之後快取過期。因此10分鐘之內訪問,將會命中強快取,如下所示:

當然,除了上述與快取直接相關的欄位外,http header中還包括如下間接相關的欄位。

Age

出現此欄位,表示命中代理伺服器的快取。它指的是代理伺服器對於請求資源的已快取時間,單位為秒。如下:

Age:2383321
Date:Wed, 08 Mar 2017 16:12:42 GMT

以上指的是,代理伺服器在2017年3月8日16:12:42時向源伺服器發起了對該資源的請求,目前已快取了該資源2383321秒。

Date

指的是響應生成的時間。請求經過代理伺服器時,返回的Date未必是最新的,通常這個時候,代理伺服器將增加一個Age欄位告知該資源已快取了多久。

Vary

對於伺服器而言,資原始檔可能不止一個版本,比如說壓縮和未壓縮,針對不同的客戶端,通常需要返回不同的資源版本。比如說老式的瀏覽器可能不支援解壓縮,這個時候,就需要返回一個未壓縮的版本; 對於新的瀏覽器,支援壓縮,返回一個壓縮的版本,有利於節省頻寬,提升體驗。那麼怎麼區分這個版本呢,這個時候就需要Vary了。

伺服器通過指定Vary: Accept-Encoding,告知代理伺服器,對於這個資源,需要快取兩個版本: 壓縮和未壓縮。這樣老式瀏覽器和新的瀏覽器,通過代理,就分別拿到了未壓縮和壓縮版本的資源,避免了都拿同一個資源的尷尬。

Vary:Accept-Encoding,User-Agent

如上設定,代理伺服器將針對是否壓縮和瀏覽器型別兩個維度去快取資源。如此一來,同一個url,就能針對PC和Mobile返回不同的快取內容。

怎麼讓瀏覽器不快取靜態資源

實際上,工作中很多場景都需要避免瀏覽器快取,除了瀏覽器隱私模式,請求時想要禁用快取,還可以設定請求頭: Cache-Control: no-cache, no-store, must-revalidate 。

當然,還有一種常用做法: 即給請求的資源增加一個版本號,如下:

這樣做的好處就是你可以自由控制什麼時候載入最新的資源。

不僅如此,HTML也可以禁用快取,即在頁面的

上述雖能禁用快取,但只有部分瀏覽器支援,而且由於代理不解析HTML文件,故代理伺服器也不支援這種方式。

IE8的異常表現

實際上,上述快取有關的規律,並非所有瀏覽器都完全遵循,比如IE8。

資源快取是否有效相關。

瀏覽器

前提

操作

表現

正常表現

IE8

資源快取有效

新開一個視窗載入網頁

重新發送請求(返回200)

展示快取的頁面

IE8

資源快取失效

原瀏覽器視窗中單擊 Enter 按鈕

展示快取的頁面

重新發送請求(返回200)

Last-Modified / E-Tag 相關。

瀏覽器

前提

操作

表現

正常表現

IE8

資源內容沒有修改

新開一個視窗載入網頁

瀏覽器重新發送請求(返回200)

重新發送請求(返回304)

IE8

資源內容已修改

原瀏覽器視窗中單擊 Enter 按鈕

瀏覽器展示快取的頁面

重新發送請求(返回200)

本問就討論這麼多內容,有什麼問題或好的想法歡迎點選「閱讀原文」參與留言和評論。

參考文章

  • Cache Policy Interaction—Maximum Age and Maximum Staleness
  • HTTP/1.1: Header Field Definitions
  • http - What’s the difference between Cache-Control: max-age=0 and no-cache? - Stack Overflow
  • App 快取方案:Http 快取 · baitouwei
  • Cache-Control - HTTP | MDN
  • 徹底弄懂 Http 快取機制 - 基於快取策略三要素分解法