一、簡介
緩存就是建立一種自動化的、客户端和服務端協商的機制。
客户端和服務端是通過HttpHeader來傳遞協商的信息。
二、cache-control
cache-control 可以出現在Http-Request-Header中,也可以出現在Http-Response-Header中。
它是在HTTP1.1規範中添加的,優先級高於expires (在HTTP1.0規範中添加)。
1. max-age
max-age 的值是秒,不是毫秒。比如:
# 緩存 1 天
Cache-Control: max-age=86400
# 緩存 1 小時
Cache-Control: max-age=3600
# 不緩存
Cache-Control: no-cache, no-store, max-age=0
max-age 的計時起點是從響應被生成的時間開始計算的,因此它是個相對時間。具體來説:
-
對於共享緩存(如 CDN、代理服務器) :
- 從服務器生成響應的時間開始計算。
- 基於響應頭中的
Date字段。
-
對於私有緩存(如瀏覽器) :
- 從收到響應的時間開始計算。
- 實際等於從響應到達客户端的時間開始。
假設服務器在 2024-01-01 10:00:00 發送響應:
Date: Mon, 01 Jan 2024 10:00:00 GMT
Cache-Control: max-age=3600
那麼:
- 有效時間:
10:00:00+ 3600秒 =11:00:00 - 在
11:00:00之後,緩存過期
只要 max-age 的值不為0,那麼一定不會向服務端發起請求,而是從本地緩存中獲取資源。
2. s-maxage
s-maxage 跟 max-age 類似,也是指定緩存的過期時間。
但它指定的是 public 類型的緩存,比如存儲在CDN服務器上的資源,因為這種設備上面的資源就是公用的。
相對的,還有 private 類型的緩存,比如存儲在瀏覽器上的資源,這些資源只提供給使用此瀏覽器的用户。
s-maxage 一定會發送網絡請求,而且它的優先級高於 max-age 。
舉例:
# 示例1:不同緩存時間
Cache-Control: public, s-maxage=7200, max-age=300
# CDN/代理:緩存2小時
# 用户瀏覽器:緩存5分鐘
# 示例2:僅s-maxage
Cache-Control: s-maxage=3600
# CDN/代理:緩存1小時
# 用户瀏覽器:不緩存(立即過期)
# 示例3:s-maxage=0
Cache-Control: s-maxage=0, max-age=86400
# CDN/代理:立即過期,需要驗證
# 用户瀏覽器:緩存1天
# 常見錯誤:
❌ Cache-Control: private, s-maxage=3600
# s-maxage對private無效(私有內容CDN不應緩存)
❌ Cache-Control: s-maxage=3600
# 缺少max-age → 瀏覽器可能完全不緩存
✅ 正確做法:
Cache-Control: public, s-maxage=604800, max-age=86400
# 明確區分兩種緩存的過期時間
3. public
表明響應可以被任何對象(包括:發送請求的客户端,代理服務器,等等)緩存,即使是通常不可緩存的內容。(例如:1.該響應沒有max-age指令或Expires消息頭;2. 該響應對應的請求方法是 POST 。)
4. private
表明響應只能被單個用户緩存,不能作為共享緩存(即代理服務器不能緩存它)。私有緩存可以緩存響應內容,比如:對應用户的本地瀏覽器。
5. no-cache
顧名思義,no-cache 就是不使用緩存的意思嗎?
其實並不是,它的意思是説,當前的這個資源,不論情況如何,總是向服務端發起請求,由服務端通過一些協商機制(比如 Last-Modified ),驗證是否使用客户端的緩存,還是發送新的資源。
6. no-store
顧名思義, no-store 的意思就是不使用任何的緩存策略。
7. 流程圖和配置舉例
# Nginx配置示例:為靜態資源設置不同緩存策略
location ~* \.(css|js)$ {
add_header Cache-Control "public, s-maxage=31536000, max-age=86400";
# CDN緩存1年,用户緩存1天
}
location /api/ {
add_header Cache-Control "private, max-age=300";
# 僅瀏覽器緩存5分鐘,CDN不緩存
}
location /dynamic/ {
add_header Cache-Control "public, s-maxage=0, max-age=0, must-revalidate";
# 所有緩存立即過期且必須驗證
}
三、expires
expires 用來指定資源到期的時間,不同於 max-age 使用相對時間,它使用的是絕對時間戳(如:Expires: Wed, 21 Oct 2025 07:28:00 GMT)。
它告訴瀏覽器,在過期時間前可以直接從瀏覽器緩存中獲取資源,而無需發起網絡請求。
expires 和 max-age 都屬於強緩存,是性能最優的緩存策略,就是説當這兩個值生效的時候,會直接從本地緩存中讀取資源,而不會向服務端發送請求,即使服務端的資源已經發生了改變。
對於很長時間都不會發生改變的資源,應該使用強緩存策略,比如網站的logo。
但是,強緩存策略也有不足之處。比如服務端的資源已經變化了,但在客户端還是執行着強緩存策略,那麼就無法獲取到更新後的資源。
那我們如何能夠感知到服務端資源的變化呢?
四、last-modified/if-modified-since
last-modified 和 if-modified-since 是基於客户端和服務端協商的緩存機制,也稱為協商緩存。
last-modified 出現在Http-Response-Header中,而 if-modified-since 出現在Http-Request-Header中。
1. 工作流程
- 客户端向服務端請求資源,並在請求頭中帶上
if-modified-since屬性(首次請求時,沒有此屬性)。 -
服務端接收到請求後,用
if-modified-since的屬性值,跟目標資源的修改時間進行比對:- 如果時間一致,服務端返回304,意思是説,客户端你請求的資源在某個時間點(
if-modified-since的屬性值)之後沒有改變,可以直接使用本地的緩存資源。 - 如果不一致,服務端返回200,意思是説,客户端你請求的資源在某個時間點(
if-modified-since的屬性值)之後已經發生了改變,我發給你一份新的。
- 如果時間一致,服務端返回304,意思是説,客户端你請求的資源在某個時間點(
- 響應頭中帶上
last-modified屬性,其值是目標資源的修改時間。
但是,如果同時 cache-control 中還配置了 max-age :
max-age還在有效期的時候,會優先使用本地緩存中的資源。- 如果超出有效期了,才會向服務端發送請求,執行協商機制。
2. 缺點
- 某些服務端不能獲取精確的修改時間。
- 文件修改時間改了,但是文件內容卻沒有變。
五、Etag/if-none-match
Etag 和 if-none-match 也是協商緩存。
Etag 出現在Http-Response-Header中,而 if-none-match 出現在Http-Request-Header中。
跟上面提到的 last-modified 和 if-modified-since 工作流程類似,而且也都受 max-age 影響。
但是,Etag 和 if-none-match 是用文件的簽名進行比較,這樣就解決了上面提到的 last-modified 和 if-modified-since 的缺點。
而且 Etag 比 last-modified 的優先級更高。
六、總結
- 當用户使用
Ctrl+F5時,相當於所有緩存失效,瀏覽器向服務端發請求,服務端返回新數據,且狀態碼是200。 - 當用户點擊刷新,或者使用
F5時,相當於expires和cache-control失效,瀏覽器向服務端發請求,服務端驗證後,返回304或者200。 - 當
expires和cache-control有效時,瀏覽器會從本地獲取緩存的數據,並返回200(from disk cache)。