解鎖緩存新姿式——更靈活的 Cache

緩存你們族迎來了新的成員——Cache,可能考慮到 Application Cache、LocalStorage 這兩個傢伙的先天缺陷後天發育不良帶來的問題,Google 和 Firefox 對其進行了基因重組,不過兩邊在如何重組的想法上有分歧,因此各自就分別各自造了去。
前端

Chromium Cache API 設計者給 Cache API 的定位是「ServiceWorker 的一種新的應用緩存機制」,他們把 Cache API 定位爲 Application Cache(雖然離線緩存的設計上存在重大缺陷,可是代碼至少是無辜的),咱們就很容易理解爲何 Chromium 內部的 Cache API 代碼實現會大量複用 Application Cache 的代碼,使用同樣的存儲類型(Temporary),使用同樣的存儲後端(Very Simple Backend)。webpack

Firefox Cache API 設計者 在博客文章中描述了他的想法,最初是想重用 HTTP Cache 或者 基於 IndexedDB 去實現,但 Cache API 規範在不斷演進,一些規範細節與上述解決方案存在不可調和的衝突。好比,HTTP Cache 中,一個 URL 只能對應一個 Response,但 Cache API 規範要求同一 URL(不一樣的 Header)能夠對應多個 Response,另外,HTTP Cache 沒有使用容量管理系統(QuotaManager)而 Cache API 須要使用。IndexedDB 基於結構克隆(structured cloning),還不支持流式數據(streaming data),這樣,Response可能會很是大,從網絡回來會很是慢,會明顯增大內存使用。基於上述緣由,Firefox 決定基於 SQLite 爲 Cache API 實現一套新的存儲機制。web

你須要知道 CacheStorage算法

Cache 對象受到 CacheStorage 的管理,在 W3C 規範中,CacheStorage 對應到內核的 ServiceWorkerCacheStorage 對象。它提供了不少JS接口用於操做Cache 對象:後端

  • CacheStorage.open() 用於獲取一個 Cache 對象實例。
  • CacheStorage.match() 用於檢查 CacheStorage 中是否存在以Request 爲 key 的 Cache 對象。
  • CacheStorage.has() 用於檢查是否存在指定名稱的 Cache 對象。
  • CacheStorage.keys() 用於返回 CacheStorage 中全部 Cache 對象的 cacheName 列表。
  • CacheStorage.delete() 用於刪除指定 cacheName 的 Cache 對象。

在使用過程當中,須要注意如下這些狀況:跨域

  • 任意 CacheStorage 方法的調用,都有機會引發建立 ServiceWorkerCacheStorage 對象。
  • ServiceWorkerCacheStorageManager 維護一個 cache_storage_map_(std::map<GURL, ServiceWorkerCacheStorage*>),這個 map 管理了全部的 origin + ServiceWorkerCacheStorage。
  • 任何一個域名(好比,origin: https://chaoshi.m.tmall.com/)只會建立一個 ServiceWorkerCacheStorage 對象。
  • ServiceWorkerCacheStorage 維護一個 cache_map_(std::map<std::string, base::WeakPtr<ServiceWorkerCache> >),這個 map 管理了同一 origin 下全部的 cacheName + ServiceWorkerCache。
  • 同一域名下的 ServiceWorkerCacheStorage 都放在同一目錄,目錄路徑 storage_path: /data/data/com.UCMobile/app_core_ucmobile/Service Worker/CacheStorage/8f9fa7c394456a3f75c7c0aca39d897179ba4003。其中8f9fa7c394456a3f75c7c0aca39d897179ba4003 是 origin(https://chaoshi.m.tmall.com/)的 hash 值。

前端從這些狀況能夠獲得哪些信息呢?資源的存儲不是按照資源的域名處理的,而是按照 Service Worker 的 origin 來處理,因此 Cache 的資源是沒法跨域共享的,意思就是說,不一樣域的 Service Worker 沒法共享使用對方的 Cache,即便是 Foreign Cache 請求的跨域資源,一樣也是存放在這個 origin 之下。由於 ServiceWorkerCache 經過 cacheName 標記緩存版本,因此就會存在多個版本的 ServiceWorkerCache 資源。爲何須要 cacheName 來標記版本呢?瀏覽器

假設當前域名下全部的覆蓋式發佈的靜態資源和接口數據所有存儲在同一個 cacheName 裏面,業務部署更新後,沒法識別舊的冗餘資源,單靠前端沒法徹底清除。這是由於 Service Worker 不知道完整的靜態資源路徑表,只能在客戶端發起請求時去作判斷,那些當前不會用到的資源不表明之後必定不會使用到。假如靜態資源是非覆蓋式發佈,那麼冗餘的資源就更多了。這裏要特別注意的是,Cache 不會過時,只能顯式刪除緩存

若是版本更新後,更換 cacheName,這意味着舊 cacheName 的資源是不會使用到了,業務邏輯能夠放心的把舊 cacheName 對應的全部資源所有清除,而無需知道完整的靜態資源路徑表。網絡

那 cacheName 是否是隻是在這種狀況下才能發揮做用呢?其實不是的,使用過 webpack 工具的同窗知道 vender 配置,vender 主要是把最不常常變更的第三方的庫文件打包在一塊兒,避免與頻繁更新的資源打包一塊兒,提升客戶端緩存使用率,還有就是 common 的配置,把公用的組件打包在一塊兒,減小代碼冗餘,所以,cacheName 也能夠根據這種狀況進行設置,最大化利用緩存空間,提升緩存利用率。app

因爲 Service Worker 相關緩存的底層存儲都使用了系統的文件系統(File System),而文件系統通常是不支持多進程訪問的,當統一域名下有兩個不一樣的 Service Worker 是沒法同時對同一資源進行操做的。

你更須要知道 Cache

規範裏 Cache 對應內核的 ServiceWorkerCache 對象,提供了已緩存的 Request / Response 對象體的存儲管理機制。它提供了一系列管理存儲的JS接口:

  • Cache.put() 用於把 Request / Response 對象體放進指定的 Cache。
  • Cache.add() 用於獲取一個 Request 的 Response,並將 Request / Response 對象體放進指定的 Cache。注:等價於 fetch(request) + Cache.put(request, response)。
  • Cache.addAll() 用於獲取一組 Request 的 Response,並將該組 Request / Response 對象體放進指定的Cache。
  • Cache.keys() 用於獲取 Cache 中全部 key。
  • Cache.match() 用於查找是否存在以 Request 爲 key 的Cache 對象。
  • Cache.matchAll() 用於查找是否存在一組以 Request 爲 key 的 Cache 對象組。
  • Cache.delete() 用於刪除以 Request 爲 key 的 Cache Entry。注意,Cache 不會過時,只能顯式刪除 。

ServiceWorkerCache 對應的存儲目錄是 /data/data/com.UCMobile/app_core_ucmobile/Service Worker/CacheStorage/8f9fa7c394456a3f75c7c0aca39d897179ba4003/7353b21ee437f3877043ae17a5d5ba6395fdbd31,其中 7353b21ee437f3877043ae17a5d5ba6395fdbd31 是 cacheName(tm/chaoshi-fresh/4.2.17)的 hash 值(使用 base::SHA1HashString 計算)。相同的資源名稱,若是 cacheName 不一樣,是會分開存儲的哦。

說到存儲路徑,那必然會涉及到存儲的容量大小,Service Worker 規範並無明確規定 ServiceWorkerCache 的容量限制,在 Chromium 50 如下版本的內核限制爲 512M,Chromium 50 及以上版本內核不做限制(即爲std::numeric_limits<int>::max)。固然,這只是 Service Worker 層面的限制,它還會受瀏覽器 QuotaManager 的限制。

QuotaManager 對每一個域名可用存儲空間也有限制,算法(Chromium57)可簡單描述以下:

Temporary 類型存儲限額 = 【系統磁盤可用空間(available_disk_space) + 瀏覽器全局已使用空間(global_limited_usage)】/ 3 (注:kTemporaryQuotaRatioToAvail = 3)

每一個域名可以使用 Temporary 類型存儲限額 = Temporary 類型存儲限額 / 5 (注:QuotaManager::kPerHostTemporaryPortion = 5)

好比,系統磁盤可用空間爲 570M, 瀏覽器全局已使用空間爲 30M,那麼 每一個域名可以使用 Temporary 類型存儲限額 = (570+30)/ 3 / 5 = 40M。雖然 ServiceWorkerCache 在 Service Worker 層面的限制爲 512M,很是大,但它也不能超出每一個域名的限制(40M),即同一域名下的 ServiceWorkerCache 也只能使用 40M。

通常來講,Service Worker 層面對 ServiceWorkerCache 的限制都會大於瀏覽器對每一個域名的限制,因此,一般可理解爲,ServiceWorkerCache 僅受瀏覽器 QuotaManager 對域名可以使用存儲的限制。對於前端開發同窗來講,必須有清理冗餘緩存的業務邏輯,而且提升緩存資源的使用率。

當 Service Worker 從 Cache 拿不到資源時,就會去 http cache 查找,找不到纔去請求網絡。

目前對 Cache API 的使用比較有限,後面有經驗積累再繼續補充。

相關文章
相關標籤/搜索