博客 / 詳情

返回

瀏覽器緩存策略

一、簡介

緩存就是建立一種自動化的客户端和服務端協商的機制。

客户端和服務端是通過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 的計時起點是從響應被生成的時間開始計算的,因此它是個相對時間。具體來説:

  1. 對於共享緩存(如 CDN、代理服務器) :

    • 從服務器生成響應的時間開始計算。
    • 基於響應頭中的 Date 字段。
  2. 對於私有緩存(如瀏覽器) :

    • 從收到響應的時間開始計算。
    • 實際等於從響應到達客户端的時間開始。

假設服務器在 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-maxagemax-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)。

它告訴瀏覽器,在過期時間前可以直接從瀏覽器緩存中獲取資源,而無需發起網絡請求

expiresmax-age 都屬於強緩存,是性能最優的緩存策略,就是説當這兩個值生效的時候,會直接從本地緩存中讀取資源,而不會向服務端發送請求,即使服務端的資源已經發生了改變。

對於很長時間都不會發生改變的資源,應該使用強緩存策略,比如網站的logo。

但是,強緩存策略也有不足之處。比如服務端的資源已經變化了,但在客户端還是執行着強緩存策略,那麼就無法獲取到更新後的資源。

那我們如何能夠感知到服務端資源的變化呢?

四、last-modified/if-modified-since

last-modifiedif-modified-since 是基於客户端和服務端協商的緩存機制,也稱為協商緩存

last-modified 出現在Http-Response-Header中,而 if-modified-since 出現在Http-Request-Header中。

1. 工作流程

  1. 客户端向服務端請求資源,並在請求頭中帶上 if-modified-since 屬性(首次請求時,沒有此屬性)。
  2. 服務端接收到請求後,用 if-modified-since 的屬性值,跟目標資源的修改時間進行比對:

    • 如果時間一致,服務端返回304,意思是説,客户端你請求的資源在某個時間點( if-modified-since 的屬性值)之後沒有改變,可以直接使用本地的緩存資源。
    • 如果不一致,服務端返回200,意思是説,客户端你請求的資源在某個時間點( if-modified-since 的屬性值)之後已經發生了改變,我發給你一份新的。
  3. 響應頭中帶上 last-modified 屬性,其值是目標資源的修改時間。

但是,如果同時 cache-control 中還配置了 max-age

  • max-age 還在有效期的時候,會優先使用本地緩存中的資源。
  • 如果超出有效期了,才會向服務端發送請求,執行協商機制。

2. 缺點

  • 某些服務端不能獲取精確的修改時間。
  • 文件修改時間改了,但是文件內容卻沒有變

五、Etag/if-none-match

Etagif-none-match 也是協商緩存。

Etag 出現在Http-Response-Header中,而 if-none-match 出現在Http-Request-Header中。

跟上面提到的 last-modifiedif-modified-since 工作流程類似,而且也都受 max-age 影響。

但是,Etagif-none-match 是用文件的簽名進行比較,這樣就解決了上面提到的 last-modifiedif-modified-since 的缺點。

而且 Etaglast-modified優先級更高

六、總結

  • 當用户使用 Ctrl + F5 時,相當於所有緩存失效,瀏覽器向服務端發請求,服務端返回新數據,且狀態碼是200。
  • 當用户點擊刷新,或者使用 F5 時,相當於 expirescache-control 失效,瀏覽器向服務端發請求,服務端驗證後,返回304或者200。
  • expirescache-control 有效時,瀏覽器會從本地獲取緩存的數據,並返回200(from disk cache)。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.