Stories

Detail Return Return

ServiceWorker 緩存與 HTTP 緩存 - Stories Detail

雖然 ServiceWorker 和 PWA 正在成為現代 Web 應用程序的標準,但瀏覽器資源緩存變得比以往任何時候都複雜。
本文涵蓋了瀏覽器緩存的重點內容,具體包括:

  1. ServiceWorker 緩存與 HTTP 緩存的優先級?
  2. 主流瀏覽器實現的 MemoryCache 和 DiskCache 在哪一層?
  3. MemoryCache、DiskCache、ServiceWorker 緩存哪個速度更快?

緩存流程概述

我們先來看標準定義的資源請求遵循的順序:

緩存流

  1. ServiceWorker 緩存:ServiceWorker 檢查資源是否存在其緩存中,並根據其編程的緩存策略決定是否返回資源。這個操作不會自動發生,需要在註冊的 ServiceWorker 中定義 fetch 事件去攔截並處理網絡請求,這樣才能命中 ServiceWorker 緩存而不是網絡或者 HTTP 緩存。
  2. HTTP 緩存:這裏就是我們常常説的「強緩存」和「協商緩存」,如果 HTTP 緩存未過期的話,瀏覽器就會使用 HTTP 緩存的資源。
  3. 服務器端:如果 ServiceWorker 緩存或者 HTTP 緩存中未找到任何資源,則瀏覽器會向網絡請求資源。這裏就會涉及到 CDN 服務或者源服務的工作了。

這是標準定義的資源請求流程,但是有追求的瀏覽器還會在 ServiceWorker 上面加一層 「內存緩存層」 ,以 Chrome 為例,我們請求一個資源,除去網絡,會有三種瀏覽器緩存返回:

image-20220420193610795

那麼 MemoryCache 和 DiskCache 與 ServiceWorker Cache 的優先級是怎麼樣的呢?
下面我們講講三者的區別。

MemoryCache、DiskCache 在緩存流程的哪一層?

我們以 Chrome 為例,MemoryCache 作為第一公民,位於 ServiceWorker 之上。
也就是命中了 MemoryCache,就不會觸發 ServiceWorker 的 fetch 事件。
而 DiskCache 則位於原來的 HTTP 緩存層:

http-cache-priority.drawio

MemoryCache 的存在會導致一個問題: ServiceWorker 並不總是對資源有着控制權。
這會另我們本來期望的情況會變得複雜且不可預知。可惜的是 MemoryCache 並不在 W3C 的標準中,W3C 從 2016 年到現在仍然在討論着這個事情,看來短時間這個問題是得不到解決了。

一些正在討論的話題:

  1. safari fetches from memory cache instead of Service worker
  2. Difference between disk and memory cache
  3. Advanced Questions About Service Worker
  4. allow service worker produced resources to be marked as "cachable"

我們真的沒有辦法麼?

要是我們遇到業務場景,確實對 ServiceWorker 資源控制權有很強的的要求,我們還是可以做點事情的。
MemoryCache 是受控於 「強緩存」 的,這意味着我們可以在 ServiceWorker 攔截資源的響應,並設置資源響應頭來使資源從 MemoryCache 失效:

cache-control: max-age=0
self.addEventListener("fetch", (event) => {
  event.respondWith(
    (async function () {
      // 從 HTTP 緩存或者網絡獲取資源
      const res =  fetch(event.request);
      
      // 因為 Response 是一個流,只能用一次,所以這裏要 clone 一下。
      const newRes = res.clone();
      
      // 改寫資源響應頭
            return new Response(res.body, { ...newRes, headers: {
        'cache-control': 'max-age=0'
      }});
    })();
  );
});

需要注意的是,這種方法是以犧牲少量加載性能為前提的。這取決於我們實際場景中是性能優先,還是離線優先,或者其他什麼情況優先。

MemoryCache、DiskCache、ServiceWorker 緩存哪個速度更快?

image-20220420203359745

我們再看一下同一個資源三種緩存的加載速度和優先級:

  1. 加載速度:MemoryCache > DiskCache > ServiceWorker
  2. 優先級:MemoryCache > ServiceWorker> DiskCache

MemoryCache 優先級在 ServiceWorker 前面,這個沒問題。
但是速度更慢的 ServiceWorker 優先級比速度更快的 DiskCache 更高?
那盤下來,ServiceWorker 豈不是減慢了站點的加載速度?

對照實驗

為了研究這個問題,我做了一組對照實驗。
實驗只在 Chrome 進行,chrome devtool 為每個資源提供時間。所有加載資源的信息都可以作為 HAR 文件下載下來,然後編寫本地腳本進行信息提取和分析。

image-20220421001201494

實驗條件

  1. 同一個環境:Chrome97 / MacOS 10.14 / Wifi
  2. 同一張圖片的多次併發加載:

    1. 3 張 133KB 圖片 10 次實驗
    2. 10 張 133KB 圖片 10 次實驗
    3. 100 張 133KB 圖片 10 次實驗
  3. 觀察兩個性能:

    1. DiskCache 緩存性能表現
    2. ServiceWorker 緩存速度表現

實驗一:3 張 133KB 圖片併發

image-20220421013805667

首先是併發請求 3 張圖片進行 10 次實驗,取平均數據,然後分別觀察 DiskCache、ServiceWorker Cache 的性能表現。

觀察:

  1. DiskCache:我們發現下載操作並沒有花太多時間,但是資源在等待排隊。
  2. ServiceWorker Cache:更多耗時在下載。

結論:但儘管如此,這種情況下, DiskCache 依然是比 ServiceWorker Cache 更快。

實驗二:3 張 133KB 圖片 10 次實驗

image-20220421013924399

當我把併發圖片增加到 10 張,這種情況可能會更加接近於實際情況,站點中可能會擁有更多的不同的資源(JS文件、字體、樣式、圖像等),因為某些網站可能會在一個頁面存在超過 10 個資源。

觀察:

  1. DiskCache:從第二個資源開始排隊時間依然很長,但是下載時間是基本不變的。
  2. ServiceWorker Cache: 排隊並不是問題,但等待是。

結論:這種情況下, DiskCache 會略遜於 ServiceWorker Cache。

實驗三:3 張 133KB 圖片 100 次實驗

image-20220421014006376

當我把併發圖片增加到 100 張,這種情況幾乎是不真實的情況,但是我好奇為什麼 DiskCache 為什麼在第一次試驗中比 ServiceWorker Cache 更快。

觀察:

  1. DiskCache:排隊依然是問題,且隨着併發數成線性上升。我們甚至能看到瀏覽器是如何加載圖片的,一次併發大概 6 張圖片。
  2. ServiceWorker Cache:雖然等待時間隨着併發數上升,但是是平緩的。

結論: 大併發下 ServiceWorker Cache 比 DiskCache 更快。

那 DiskCache 和 ServiceWorker 怎麼選擇?
小孩子才做選擇,大人都要

由於 ServiceWorker 的優先級在 DiskCache 之上,我們可以在 ServiceWorker 進行 「資源競速」,同一時間請求 ServiceWorker Cache 和 DiskCache,哪個先返回就把資源返回上一層。代碼可能是這樣的:

self.addEventListener("fetch", (event) => {
  event.respondWith(
    (async function () {
      const res = Promise.race([
        // 請求 ServiceWorker Cache
        cache.open(CACHE_NAME).then(cache => cache.match(event.request)),
        // 請求 DiskCache 或者網絡資源
        fetch(event.request)
      ])
    })();
  );
});

實驗四:資源競速之後,併發請求 3 張圖片、10 張圖片 和 100 張圖片

當我們進行資源競速之後,這種情況下,無論是併發少量資源還是大量資源,都能達到最快的級別。

image-20220421014045092
image-20220421005715401

總結

本篇我們搞懂了 ServiceCache、MemoryCache、DiskCache 的優先級。
然後深入對比了 ServiceWorker Cache 和 DiskCache 的性能表現。
在少量資源併發的時候,DiskCache 更快,在大量資源併發的時候,ServiceWorker 更快。
最後通過「資源競速」的方式來兼顧兩種情況。
但是,在某些時候,我們比較 ServiceWorker 和 HTTP 緩存有點不公平。
ServiceWorker 的用途會更加廣泛,它提供了更細力度的緩存控制、使離線化應用得以實現、並且對比主線程,他能夠使用更多的 CacheAPI 容量。

user avatar chenwl Avatar
Favorites 1 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.