每個瀏覽器都自帶了 HTTP 緩存實現(xiàn)功能。您只需要確保每個服務器響應都提供正確的 HTTP 標頭指令,以指示瀏覽器何時可以緩存響應以及可以緩存多久。緩存并重復利用之前獲取的資源是優(yōu)化性能的一個重要方面。
在 http1.0 時代,給客戶端設定緩存方式可通過兩個字段——Pragma和Expires來規(guī)范。
一,Pragma
有 Pragma:no-cache;表示禁用緩存;
二,Expires
exprires是啟用緩存 的方式,與Pragma剛好相對應
正確的格式是 expries:GMT時間格式;( Expires的值對應一個GMT(格林尼治時間),比如Mon, 22 Jul 2017 11:12:01 GMT來告訴瀏覽器資源緩存過期時間,如果還沒過該時間點則不發(fā)請求。)
上述兩種方式如果同時存在,Pragma的優(yōu)先級大于Expires。
針對上述的“Expires時間是相對服務器而言,無法保證和客戶端時間統(tǒng)一”的問題,http1.1新增了 Cache-Control 來定義緩存過期時間。
三,Cache-Control
- 每個資源都可通過 Cache-Control HTTP 標頭定義其緩存策略
- Cache-Control 指令控制誰在什么條件下可以緩存響應以及可以緩存多久。
從性能優(yōu)化的角度來說,最佳請求是無需與服務器通信的請求:您可以通過響應的本地副本消除所有網(wǎng)絡延遲,以及避免數(shù)據(jù)傳送的流量費用。為實現(xiàn)此目的,HTTP 規(guī)范允許服務器返回 Cache-Control 指令,這些指令控制瀏覽器和其他中間緩存如何緩存各個響應以及緩存多久。
注:Cache-Control 標頭是在 HTTP/1.1 規(guī)范中定義的,取代了之前用來定義響應緩存策略的標頭(例如 Expires)。所有現(xiàn)代瀏覽器都支持 Cache-Control,因此,使用它就夠了。

注意:若報文中同時出現(xiàn)了 Expires 和 Cache-Control,則以 Cache-Control 為準。
也就是說優(yōu)先級從高到低分別是 Pragma -> Cache-Control -> Expires 。
Cache-Control也是一個通用首部字段,這意味著它能分別在請求報文和響應報文中使用。在RFC中規(guī)范了 Cache-Control 的格式為:
"Cache-Control" ":" cache-directive
作為請求首部時,cache-directive 的可選值有:

作為響應首部時,cache-directive 的可選值有:

Cache-Control 允許自由組合可選值,例如:
Cache-Control: max-age=3600, must-revalidate
定義最佳 Cache-Control 策略

按照以上決策樹為您的應用使用的特定資源或一組資源確定最佳緩存策略。在理想的情況下,您的目標應該是在客戶端上緩存盡可能多的響應,緩存盡可能長的時間,并且為每個響應提供驗證令牌,以實現(xiàn)高效的重新驗證。
如果客戶端向服務器發(fā)了請求,那么是否意味著一定要讀取回該資源的整個實體內容呢?
我們試著這么想——客戶端上某個資源保存的緩存時間過期了,但這時候其實服務器并沒有更新過這個資源,如果這個資源數(shù)據(jù)量很大,客戶端要求服務器再把這個東西重新發(fā)一遍過來,是否非常浪費帶寬和時間呢?
答案是肯定的,那么是否有辦法讓服務器知道客戶端現(xiàn)在存有的緩存文件,其實跟自己所有的文件是一致的,然后直接告訴客戶端說“這東西你直接用緩存里的就可以了,我這邊沒更新過呢,就不再傳一次過去了”。
為了讓客戶端與服務器之間能實現(xiàn)緩存文件是否更新的驗證、提升緩存的復用率,Http1.1新增了幾個首部字段來做這件事情。
1.Last-Modified
服務器將資源傳遞給客戶端時,會將資源最后更改的時間以“Last-Modified: GMT”的形式加在實體首部上一起返回給客戶端
客戶端會為資源標記上該信息,下次再次請求時,會把該信息附帶在請求報文中一并帶給服務器去做檢查,若傳遞的時間值與服務器上該資源最終修改時間是一致的,則說明該資源沒有被修改過,直接返回304狀態(tài)碼,內容為空,這樣就節(jié)省了傳輸數(shù)據(jù)量 。如果兩個時間不一致,則服務器會發(fā)回該資源并返回200狀態(tài)碼,和第一次請求時類似。

這樣保證不向客戶端重復發(fā)出資源,也保證當服務器有變化時,客戶端能夠得到最新的資源。一個304響應比一個靜態(tài)資源通常小得多,這樣就節(jié)省了網(wǎng)絡帶寬。
Last-Modified 存在一定問題,如果在服務器上,一個資源被修改了,但其實際內容根本沒發(fā)生改變,會因為Last-Modified時間匹配不上而返回了整個實體給客戶端(即使客戶端緩存里有個一模一樣的資源)。
2.ETag
為了解決上述Last-Modified可能存在的不準確的問題,Http1.1還推出了 ETag 實體首部字段。 服務器會通過某種算法,給資源計算得出一個唯一標志符(比如md5標志),在把資源響應給客戶端的時候,會在實體首部加上“ETag: 唯一標識符”一起返回給客戶端。例如:
Etag: "5d8c72a5edda8d6a:3239"
客戶端會保留該 ETag 字段,并在下一次請求時將其一并帶過去給服務器。服務器只需要比較客戶端傳來的ETag跟自己服務器上該資源的ETag是否一致,就能很好地判斷資源相對客戶端而言是否被修改過了。
如果服務器發(fā)現(xiàn)ETag匹配不上,那么直接以常規(guī)GET 200回包形式將新的資源(當然也包括了新的ETag)發(fā)給客戶端;如果ETag是一致的,則直接返回304知會客戶端直接使用本地緩存即可。
- 服務器使用 ETag HTTP 標頭傳遞驗證令牌。
- 驗證令牌可實現(xiàn)高效的資源更新檢查:資源未發(fā)生變化時不會傳送任何數(shù)據(jù)。
這正是驗證令牌(在 ETag 標頭中指定)旨在解決的問題。服務器生成并返回的隨機令牌通常是文件內容的哈希值或某個其他指紋??蛻舳瞬恍枰私庵讣y是如何生成的,只需在下一次請求時將其發(fā)送至服務器。如果指紋仍然相同,則表示資源未發(fā)生變化,您就可以跳過下載。

在上例中,客戶端自動在“If-None-Match” HTTP 請求標頭內提供 ETag 令牌。服務器根據(jù)當前資源核對令牌。如果它未發(fā)生變化,服務器將返回“304 Not Modified”響應,告知瀏覽器緩存中的響應未發(fā)生變化,可以再延用 120 秒。請注意,您不必再次下載響應,這節(jié)約了時間和帶寬。
作為網(wǎng)絡開發(fā)者,您如何利用高效的重新驗證?瀏覽器會替我們完成所有工作:它會自動檢測之前是否指定了驗證令牌,它會將驗證令牌追加到發(fā)出的請求上,并且它會根據(jù)從服務器接收的響應在必要時更新緩存時間戳。我們唯一要做的就是確保服務器提供必要的 ETag 令牌。檢查您的服務器文檔中有無必要的配置標志。
不過,如果您想更新或廢棄緩存的響應,該怎么辦?
注意:假定您已告訴訪問者將某個 CSS 樣式表緩存長達 24 小時 (max-age=86400),但設計人員剛剛提交了一個您希望所有用戶都能使用的更新。您該如何通知擁有現(xiàn)在“已過時”的 CSS 緩存副本的所有訪問者更新其緩存?在不更改資源網(wǎng)址的情況下,您做不到。
瀏覽器緩存響應后,緩存的版本將一直使用到過期(由 max-age 或 expires 決定),或一直使用到由于某種其他原因從緩存中刪除,例如用戶清除了瀏覽器緩存。因此,構建網(wǎng)頁時,不同的用戶可能最終使用的是文件的不同版本;剛獲取了資源的用戶將使用新版本的響應,而緩存了早期(但仍有效)副本的用戶將使用舊版本的響應。
所以,如何才能魚和熊掌兼得:客戶端緩存和快速更新?
您可以在資源內容發(fā)生變化時更改它的網(wǎng)址,強制用戶下載新響應。通常情況下,可以通過在文件名中嵌入文件的指紋或版本號來實現(xiàn) - 例如 style.x234dff.css。

因為能夠定義每個資源的緩存策略,所以您可以定義“緩存層次結構”,這樣不但可以控制每個響應的緩存時間,還可以控制訪問者看到新版本的速度。為了進行說明,我們一起分析一下上面的示例:
- HTML 被標記為“no-cache”,這意味著瀏覽器在每次請求時都始終會重新驗證文檔,并在內容變化時獲取最新版本。此外,在 HTML 標記內,您在 CSS 和 JavaScript 資產的網(wǎng)址中嵌入指紋:如果這些文件的內容發(fā)生變化,網(wǎng)頁的 HTML 也會隨之改變,并會下載 HTML 響應的新副本。
- 允許瀏覽器和中間緩存(例如 CDN)緩存 CSS,并將 CSS 設置為 1 年后到期。請注意,您可以放心地使用 1 年的“遠期過期”,因為您在文件名中嵌入了文件的指紋:CSS 更新時網(wǎng)址也會隨之變化。
- JavaScript 同樣設置為 1 年后到期,但標記為 private,這或許是因為它包含的某些用戶私人數(shù)據(jù)是 CDN 不應緩存的。
- 圖像緩存時不包含版本或唯一指紋,并設置為 1 天后到期。
您可以組合使用 ETag、Cache-Control 和唯一網(wǎng)址來實現(xiàn)一舉多得:較長的過期時間、控制可以緩存響應的位置以及隨需更新。
不存在什么最佳緩存策略。
您需要根據(jù)通信模式、提供的數(shù)據(jù)類型以及應用特定的數(shù)據(jù)更新要求,為每個資源定義和配置合適的設置,以及整體的“緩存層次結構”。
在制定緩存策略時,您需要牢記下面這些技巧和方法:
- 使用一致的網(wǎng)址:如果您在不同的網(wǎng)址上提供相同的內容,將會多次獲取和存儲這些內容。提示:請注意,網(wǎng)址區(qū)分大小寫。
- 確保服務器提供驗證令牌 (ETag):有了驗證令牌,當服務器上的資源未發(fā)生變化時,就不需要傳送相同的字節(jié)。
- 為每個資源確定最佳緩存周期:不同的資源可能有不同的更新要求。為每個資源審核并確定合適的 max-age。
- 確定最適合您的網(wǎng)站的緩存層次結構:您可以通過為 HTML 文檔組合使用包含內容指紋的資源網(wǎng)址和短時間或 no-cache 周期,來控制客戶端獲取更新的速度。
- 最大限度減少攪動:某些資源的更新比其他資源頻繁。如果資源的特定部分(例如 JavaScript 函數(shù)或 CSS 樣式集)會經常更新,可以考慮將其代碼作為單獨的文件提供。這樣一來,每次獲取更新時,其余內容(例如變化不是很頻繁的內容庫代碼)可以從緩存獲取,從而最大限度減少下載的內容大小。
參考資料
HTTP 緩存
HTTP緩存控制小結