博客 / 詳情

返回

深入理解瀏覽器的緩存機制

引言

瀏覽器緩存,一個經久不衰的話題。

先來看一下百度百科對它的定義:

瀏覽器緩存(Browser Caching)是為了節約網絡的資源加速瀏覽,瀏覽器在用户磁盤上對最近請求過的文檔進行存儲,當訪問者再次請求這個頁面時,瀏覽器就可以從本地磁盤顯示文檔,這樣就可以加速頁面的閲覽。

緩存可以説是性能優化中簡單高效的一種優化方式了。一個優秀的緩存策略可以縮短網頁請求資源的距離,減少延遲,並且由於緩存文件可以重複利用,還可以減少帶寬,降低網絡負荷。

本篇文章會從緩存位置、緩存過程分析、緩存類型、緩存機制、緩存策略以及用户行為對瀏覽器緩存的影響幾方面帶你一步步深入瞭解瀏覽器緩存。

緩存位置

從緩存位置上來説分為四種,並且各自有優先級,當依次查找緩存且都沒有命中的時候,才會去請求網絡。

  • Service Worker
  • Memory Cache
  • Disk Cache
  • Push Cache

Service Worker

Service Worker 是運行在瀏覽器背後的獨立線程,一般可以用來實現緩存功能。使用 Service Worker 的話,傳輸協議必須為 HTTPS。因為 Service Worker 中涉及到請求攔截,所以必須使用 HTTPS 協議來保障安全。

Memory Cache

Memory Cache 也就是內存中的緩存,主要包含的是當前頁面中已經抓取到的資源,例如頁面上已經下載的樣式、腳本、圖片等。讀取內存中的數據肯定比磁盤快,內存緩存雖然讀取高效,可是緩存持續性很短,會隨着進程的釋放而釋放(一旦我們關閉 Tab 頁面,內存中的緩存也就被釋放了)。

內存緩存中有一塊重要的緩存資源是 preloader 相關指令(例如<link rel="prefetch">)眾所周知 preloader 的相關指令已經是頁面優化的常見手段之一,它可以一邊解析 js/css 文件,一邊網絡請求下一個資源。

Disk Cache

Disk Cache 也就是存儲在硬盤中的緩存,讀取速度雖然慢點,但是什麼都能存儲到磁盤中,與 Memory Cache 相比,優勢是容量和存儲時效性。

在所有瀏覽器緩存中,Disk Cache 覆蓋面基本上是最大的。它會根據 HTTP Header 中的字段判斷哪些資源緩存(不用慌,關於 HTTP 的協議頭中的緩存字段,會在下面詳細介紹的),哪些資源可以不請求直接使用,哪些資源已經過期需要重新請求。並且即使在跨站點的情況下,相同地址的資源一旦被硬盤緩存下來,就不會再次去請求數據。絕大部分的緩存都來自 Disk Cache。

瀏覽器會把哪些文件丟進內存中?哪些丟進硬盤中?

關於這點,網上説法不一,不過以下兩點比較靠得住:

  • 對於大文件來説,大概率是不存儲在內存中的
  • 當前系統內存使用率高的話,文件優先存進硬盤

Push Cache

Push Cache(推送緩存)是 HTTP/2 中的內容,當以上三種緩存都沒有命中時,它才會被使用。它只在會話(Session)中存在,一旦會話結束就被釋放,並且緩存時間也很短暫。

如果以上四種緩存都沒有命中的話,那麼只能發起請求來獲取資源了。

為了性能上的考慮,大部分的接口都應該選擇好緩存策略,通常瀏覽器緩存策略分為兩種:強緩存和協商緩存,並且緩存策略都是通過設置 HTTP Header 來實現的。

緩存過程分析

瀏覽器與服務器通信的方式為應答模式,即: 瀏覽器發起 HTTP 請求 >> 服務器響應該請求,那麼瀏覽器怎麼確定一個資源該不該緩存,如何去緩存呢?瀏覽器第一次向服務器發起該請求後拿到請求結果後,將請求結果和緩存標識存入瀏覽器緩存,瀏覽器對於緩存的處理是根據第一次請求資源時返回的響應頭來確定的。具體過程如下圖:

由上圖我們可以知道:

  • 瀏覽器每次發起請求,都會先在瀏覽器緩存中查找該請求的結果以及緩存標識。
  • 瀏覽器每次拿到返回的請求結果都會將該結果和緩存標識存入瀏覽器緩存中。

以上兩點是瀏覽器緩存機制的關鍵,它確保了每個請求的緩存存入與讀取。下面説一下瀏覽器緩存的使用規則。根據是否需要向服務器重新發起 HTTP 請求將緩存過程分為兩個部分,分別是強緩存和協商緩存。

強緩存

強緩存: 不會向服務器發起請求,直接從緩存中讀取資源,在 chrome 控制枱的 Network 選項中可以看到該請求返回 200 的狀態碼,並且size顯示from disk cachefrom memory cache。強緩存可以通過設置兩種 HTTP Header 實現: Expires 和 Cache-Control

1、 Expires

緩存過期時間,用來指定資源到期的時間,是服務端的具體時間點。也就是説,Expires=max-age + 請求時間,需要和 Last-modified 結合使用。Expires 是 Web 服務器響應消息頭字段,在響應 http 請求時告訴瀏覽器在過期時間前瀏覽器可以直接從瀏覽器緩存取數據,而無需再次請求。

Expires 是 HTTP/1 的產物,受限於本地時間,如果修改了本地時間,可能會造成緩存失效。

2、 Cache-Control

在 HTTP/1.1 中,Cache-Control 是最重要的規則,主要用於控制網頁緩存。

Cache-Control 可以在請求頭或者響應頭中設置,並且可以組合使用多種指令:

  • public: 表明響應可以被任何對象(包括:發送請求的客户端,代理服務器,等等)緩存,即使是通常不可緩存的內容(例如,該響應沒有max-age指令或Expires消息頭)。
  • private: 表明響應只能被單個用户緩存,不能作為共享緩存(即代理服務器不能緩存它)。私有緩存可以緩存響應內容。
  • no-cache: 在發佈緩存副本之前,強制要求緩存把請求提交給原始服務器進行驗證。
  • no-store: 緩存不應存儲有關客户端請求或服務器響應的任何內容。
  • max-age: 設置緩存存儲的最大週期,超過這個時間緩存被認為過期(單位秒)。與Expires相反,時間是相對於請求的時間。
  • s-maxage: 覆蓋max-age或者Expires頭,但是僅適用於共享緩存(比如各個代理),私有緩存會忽略它。
  • max-stale: 表明客户端願意接收一個已經過期的資源。可以設置一個可選的秒數,表示響應不能已經過時超過該給定的時間。
  • min-fresh: 表示客户端希望獲取一個能在指定的秒數內保持其最新狀態的響應。

3、 Expires 和 Cache-Control 兩者對比

其實這兩者差別不大,區別就在於 Expires 是 http1.0 的產物,Cache-Control 是 http1.1 的產物,兩者同時存在的話,Cache-Control 優先級高於 Expires;在某些不支持 HTTP1.1 的環境下,Expires 就會發揮用處。所以 Expires 其實是過時的產物,現階段它的存在只是一種兼容性的寫法。

強緩存判斷是否緩存的依據來自於是否超出某個時間或者某個時間段,而不關心服務器端文件是否已經更新,這可能會導致加載文件不是服務器端最新的內容,那我們如何獲知服務器端內容是否已經發生了更新呢?此時我們需要用到協商緩存策略。

協商緩存

協商緩存就是強制緩存失效後,瀏覽器攜帶緩存標識向服務器發起請求,由服務器根據緩存標識決定是否使用緩存的過程,主要有以下兩種情況:

  • 協商緩存生效,返回 304 和 Not Modified

  • 協商緩存成功,返回 200 和請求結果

協商緩存可以通過設置兩種 HTTP Header 實現: Last-Modified 和 ETag

緩存機制

強制緩存優先於協商緩存進行,若強制緩存 (Expires 和 Cache-Control) 生效則直接使用緩存,若不生效則進行協商緩存 (Last-Modified / If-Modified-Since 和 Etag / If-None-Match),協商緩存由服務器決定是否使用緩存,若協商緩存失效,那麼代表該請求的緩存失效,返回 200,重新返回資源和緩存標識,再存入瀏覽器緩存中;生效則返回 304,繼續使用緩存。具體流程圖如下:

實際場景應用緩存策略

  • 頻繁變動的資源

對於頻繁變動的資源,首先需要使用 Cache-Control: no-cache 使瀏覽器每次都請求服務器,然後配合 ETag 或者 Last-Modified 來驗證資源是否有效。這樣的做法雖然不能節省請求數量,但是能顯著減少響應數據大小。

  • 不常變化的資源

通常在處理這類資源時,給它們的 Cache-Control 配置一個很大的 max-age=31536000 (一年),這樣瀏覽器之後請求相同的 URL 會命中強制緩存。而為了解決更新的問題,就需要在文件名 (或者路徑) 中添加 hash, 版本號等動態字符,之後更改動態字符,從而達到更改引用 URL 的目的,讓之前的強制緩存失效 (其實並未立即失效,只是不再使用了而已)。

用户行為對瀏覽器緩存的影響

所謂用户行為對瀏覽器緩存的影響,指的就是用户在瀏覽器如何操作時,會觸發怎樣的緩存策略。主要有 3 種:

  • 打開網頁,地址欄輸入地址: 查找 disk cache 中是否有匹配。如有則使用;如沒有則發送網絡請求;
  • 普通刷新 (F5):因為 TAB 並沒有關閉,因此 memory cache 是可用的,會被優先使用 (如果匹配的話)。其次才是 disk cache;
  • 強制刷新 (Ctrl + F5):瀏覽器不使用緩存,因此發送的請求頭部均帶有 Cache-control: no-cache(為了兼容,還帶了 Pragma: no-cache), 服務器直接返回 200 和最新內容。

最後

你可以關注我的同名公眾號【前端森林】,這裏我會定期發一些大前端相關的前沿文章和日常開發過程中的實戰總結。當然,我也是開源社區的積極貢獻者,github地址https://github.com/Jack-cool,歡迎star!!!

前端森林公眾號二維碼.png

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

發佈 評論

Some HTML is okay.