深刻剖析瀏覽器緩存策略

前言

在訪問一個網頁時,客戶端會從服務器下載所需的資源。可是有些資源不多發生變更,例如 HTML、JS、CSS、圖片、字體文件等。若是每次加載頁面都從源服務器下載這些資源,不只會增長獲取資源的時間,也會給服務器帶來必定壓力。所以,重用已獲取的資源十分重要。將請求的資源緩存下來,下次請求同一資源時,直接使用存儲的副本,而不會再去源服務器下載。這就是咱們常說的緩存技術。git

緩存的種類不少:瀏覽器緩存、網關緩存、CDN 緩存、代理服務器緩存等。這些緩存大體能夠歸爲兩類:共享緩存和私有緩存。共享緩存可以被多個用戶使用,而私有緩存只能用於單個用戶。瀏覽器緩存只存在於每一個單獨的客戶端,所以它是私有緩存。github

本文主要介紹私有(瀏覽器)緩存。你將學習:web

  • 瀏覽器緩存的分類
  • 如何啓用和禁止緩存
  • 緩存存儲的位置
  • 如何設置緩存的過時時間
  • 緩存過時以後會發生什麼
  • 如何爲本身的應用制定合適的緩存策略
  • 調試算法

    • 如何判斷網站是否啓用了緩存
    • 如何禁用瀏覽器緩存

緩存存儲

啓用緩存

Cache-Control

瀏覽器會根據 HTTP Response Headers 中的一些字段來決定是否要緩存該資源。經過設置 Response Headers 中的 Cache-Control 和 Expires 能夠啓用緩存,這樣資源就會被緩存到客戶端。typescript

Cache-Control 能夠設置 private 、publicmax-age 、 no-cache 來啓用緩存。瀏覽器

Cache-Control: private/public
Cache-Control: max-age=300
Cache-Control: no-cache
  • private :表示該資源只能被瀏覽器緩存。
  • public :表示該資源既能被瀏覽器緩存,也能被任何中間人(好比代理服務器、CDN 等)緩存。
  • max-age :表示該資源可以被緩存的最大時間。若是設置 max-age=0 ,該資源仍然會被瀏覽器緩存,只不過馬上就過時了。
  • no-cache :該資源會被緩存,可是馬上就過時了,所以須要先和服務器確認資源是否發生變化,只有當資源沒有變化時,該緩存纔會被使用,不然須要從服務器下載。至關於 max-age=0 。

Expires

Expires 標識了緩存的具體過時時間,來控制資源什麼時候過時。經過設置 Expires 能夠啓用緩存。不過須要注意 Expires 的值是格林威治時間(Greenwich Mean Time, GMT),不是本地時間。緩存

Expires: Fri, 08 Mar 2029 08:05:59 GMT
Expires: 0 // Expires: 0 仍然會啓用緩存,只不過緩存馬上過時。

優先級

既然 Cache-Control 和 Expires 都可以啓用緩存,那麼問題來了,若是同時設置 Cache-Control: max-age=600 和 Expires: 0 ,那麼瀏覽器應該如何緩存該資源呢?答案是隻有 Cache-Control: max-age=600 生效。由於 Cache-Control 的優先級高於 Expires,若是同時設置了 Cache-Control 和 Expires,以 Cache-Control 爲準。服務器

瀏覽器的默認行爲

設置 Cache-Control 以後,能夠看到瀏覽器確實啓用了緩存(from disk cache)。以下所示:網絡

Cache-Control:max-age=604800, must-revalidate, public

Screen Shot 2019-04-01 at 5.26.58 PM.png

可是我發現,即便 Response Header 中沒有設置 Cache-Control 和 Expires,瀏覽器仍然會緩存某些資源。這是爲何呢?工具

image.png

原來當 Response Header 中有 Last-Modified 可是沒有 Cache-Control 和 Expires 時,瀏覽器會用一套本身的算法來決定這個資源會被緩存多長時間。這是瀏覽器爲了提高性能進行的優化,每一個瀏覽器的行爲可能不一致,有些瀏覽器上甚至沒有這樣的優化。所以,若是要啓用緩存,仍是應該本身設置合適的 Cache-Control 和 Expires,不要依賴瀏覽器自身的緩存算法。固然,若是在調試時發現本應該更新的文件沒有更新,也別忘了看看是否被瀏覽器緩存了。

禁止緩存

給 Cache-Control 設置 no-store 會禁止瀏覽器和中間人緩存該資源。在處理包含我的隱私數據或銀行業務數據的資源時頗有用。

Cache-Control: no-store

緩存目標對象

通常來講,瀏覽器緩存只能存儲 GET 響應,例如 HTML、JS、CSS、圖片等靜態資源。由於這些資源不常常發生變化,因此緩存能夠幫助提高獲取資源的速度。可是像一些 POST/DELETE 請求,這些請求基本上每一次都不同,所以也沒有什麼緩存的價值。

緩存位置

瀏覽器能夠在內存、硬盤中開闢一個空間用以保存請求資源的副本。咱們常常在 Dev Tools 裏面看到 Memory Cache(內存緩存)和 Disk Cache(硬盤緩存),指的就是緩存所在的位置。請求一個資源時,會按照優先級(Service Worker -> Memory Cache -> Disk Cache -> Push Cache)依次查找緩存,若是命中則使用緩存,不然發起網絡請求。這裏只介紹經常使用的 Memory Cache 和 Disk Cache。

Screen Shot 2019-04-02 at 2.24.56 PM.png

200 from Memory Cache

表示不訪問服務器,直接從內存中讀取緩存。由於緩存的資源保存在內存中,因此讀取速度較快,可是關閉進程以後,緩存的資源也會隨之銷燬。通常來講,系統不會給內存分配較大的容量,所以內存緩存通常用於存儲小文件。同時,內存緩存在有時效性要求的場景下也頗有用(好比瀏覽器的隱私模式)。

200 from Disk Cache

表示不訪問服務器,直接從硬盤中讀取緩存。與內存相比,硬盤的讀取速度較慢,可是硬盤緩存持續的時間更長,關閉進程以後,緩存的資源仍然存在。因爲硬盤的容量較大,所以通常用於存儲大文件。

總的來講就是:

內存緩存:讀取快、持續時間短、容量小

硬盤緩存:讀取慢、持續時間長、容量大

緩存分類

瀏覽器緩存通常分爲兩類:強緩存(也稱本地緩存)和協商緩存(也稱弱緩存)。斷定過程以下:

  1. 瀏覽器發送請求前,會先去緩存裏面查看是否命中強緩存,若是命中,則直接從緩存中讀取資源,不會發送請求到服務器。不然,進入下一步。
  2. 當強緩存沒有命中時,瀏覽器必定會向服務器發起請求。服務器會根據 Request Header 中的一些字段來判斷是否命中協商緩存。若是命中,服務器會返回響應,可是不會攜帶任何響應實體,只是告訴瀏覽器能夠直接從緩存中獲取這個資源。不然,進入下一步。
  3. 若是前兩步都沒有命中,則直接從服務器加載資源。

強緩存和協商緩存的共同點在於,若是命中,都是從客戶端緩存中加載資源,而不是從服務器加載資源。而不一樣點在於,強緩存不發送請求到服務器,而協商緩存會發送請求到服務器以驗證資源是否過時。普通刷新會啓用協商緩存,忽略強緩存。只有在地址欄或收藏夾輸入網址、經過連接引用資源等狀況下,瀏覽器纔會啓用強緩存。

緩存過時策略

當緩存過時以後,瀏覽器會向服務器發起 HTTP 請求,以肯定資源是否發生了變化。若是資源未改變,那麼瀏覽器會繼續使用本地的緩存資源;若是該資源已經發生變化了,那麼瀏覽器會刪除舊的緩存資源,並將新的資源緩存到本地。

過時時間

Http Response Header 裏面的 Cache-Control: max-age=xxx 和 Expires 均可以設置緩存的過時時間,可是它們有一些區別:

Expires :標識該資源過時的時間點,它是一個絕對值,即在這個時間點以後,緩存的資源過時。
max-age :標識該資源可以被緩存的最大的時間。它是一個相對值,相對於第一次請求該文檔時服務器記錄的「請求發起時間」。

雖然 Cache-Control 是 HTTP 1.1 提出來的新特性,但並非說 max-age 優於 Expires。它們都有各自的使用場景,咱們應該根據業務需求去決定使用哪個。好比當某個資源須要在特定的時間點過時時應該使用 Expires 。若是隻是爲了開啓緩存,使用 max-age 可能會更好些,由於 Cache-Control 的優先級高於 Expires。

針對應用中幾乎不會改變的文件,一般能夠設置一個較長的過時時間,以保證緩存的有效。例如圖片、CSS、JS 等靜態資源。

緩存驗證

上一小節已經提到,當瀏覽器請求一個資源時,若是發現緩存中有該資源,可是已通過期了,那麼瀏覽器就會向服務器發起 HTTP 請求,以驗證緩存的資源是否發生變化。

緩存驗證時機

何時會進行緩存驗證?

  1. 刷新頁面。通常來講,爲了確保用戶獲取到最新的數據,在刷新頁面時大部分瀏覽器都不會再使用緩存中的數據,而是發起一個請求去服務器驗證。
  2. Response Header 中設置了 Cache-control: must-revalidate。當緩存的資源過時以後,必須到源服務器去驗證,只有確認該資源沒有過時,才能繼續使用緩存。

緩存驗證器

服務器是怎麼判斷資源改變與否的呢?服務端在返回響應內容的同時,還會在 Response Header 中設置一些驗證標識,當緩存的資源過時以後,瀏覽器就會攜帶驗證標識向服務器發起請求,服務器經過對比這些標識,就能知道緩存的資源是否發生了改變。

image.png

Header 中的驗證標識字段主要有兩組:Etag 和 If-None-Match 、Last-Modified 和 If-Modified-Since 。其中,形如 If-xxx 這樣的請求首部字段,能夠稱之爲條件請求。好比只在知足某個條件的狀況下返回或上傳文件,這樣能夠節省帶寬。

 

Last-Modified

Last-Modified 就是一個驗證器。服務器在將資源返回給客戶端的同時,會將資源的最後修改時間 Last-Modified 加在 Response Header 中一塊兒返回。瀏覽器會爲資源標記上該信息,當緩存過時以後,瀏覽器會把該信息設置到 Request Header 中的 If-Modified-Since 中向服務器發起請求。

若是 If-Modified-Since 中的值和服務器上該資源最終的修改時間一致,就說明該資源沒有被修改過,服務器會直接返回 304 狀態碼,無響應實體,這樣就能夠節省傳輸的數據量。若是不一致,服務器會返回 200 狀態碼,同時和第一次 HTTP 請求同樣,返回響應實體和驗證器。

Last-Modified:Fri, 04 Jan 2019 14:00:21 GMT

Etag

服務器會經過某種算法,爲資源計算出一個惟一標識符,在把響應返回給客戶端的時候,會在 Response Header 中加上 Etag: 惟一標識符 一塊兒返回給客戶端。

Etag:"952d03d8561454120b550f0a5679a172c4822ce8"

客戶端會將 Etag 保存下來,後續請求時會將 Etag 做爲 Request Header 中 If-None-Match 的值發給服務器。經過比對客戶端發過來的 Etag 和服務器上保存的 Etag 是否一致,就可以知道資源是否發生了變化。若是資源沒有發生變化,返回 304,客戶端繼續使用緩存。若是資源已經修改,則返回 200。

制定緩存策略

緩存真的能夠說讓咱們又愛又恨。在開發時,咱們常常遇到這樣的問題:明明已經修改了這個文件,爲何沒有生效?好吧,文件被緩存了。。。可是在上線時,咱們又但願文件儘量地被瀏覽器緩存,來提升性能。所以爲本身的應用制定合適的緩存策略很是重要。

爲靜態資源設置較長緩存時間。

有些資源很長時間都不會改變,好比一些三方庫,圖片,字體文件等。能夠爲它們設置一個很長的過時時間,例如設定「一年」。

經過給文件名的惟一標識來確保文件修改生效。

有些時候爲了解決 bug,咱們可能會修改一些文件,好比應用的 CSS、JS 等。若是這些文件已經被緩存,那麼除非用戶強制刷新頁面,不然用戶只有在緩存過時以後纔有可能獲取新的文件。如何讓瀏覽器不使用緩存,而是從新下載新的文件呢?有一個辦法就是給文件名加上惟一標識,好比 Hash 或版本信息。當文件修改以後,這個惟一標識也會隨之改變。瀏覽器發現文件改變以後,就不會使用緩存了。

明確是否有資源不能被緩存。

好比一些敏感數據,若是不該該被瀏覽器緩存,須要在 Response Header 中設置 Cache-Control: no-store。

調試

如何知道請求的資源是否被緩存了?打開 Chrome 的開發者工具,咱們能夠看到 Size 這一欄下面,若是顯示文件真實的大小,則說明該文件未被緩存。若是顯示 from xxx cache,則說明該請求使用的是已被緩存的文件。以下:

Screen Shot 2019-04-03 at 11.16.58 AM.png

調試時,若是想禁用瀏覽器緩存,能夠在開發者工具上勾選 Disabel cache。

Screen Shot 2019-04-03 at 11.36.57 AM.png

最後

最後,你們能夠經過下面這張圖再回顧一下咱們剛剛講過的內容。

參考

相關文章
相關標籤/搜索