[譯] 緩存最佳實踐

原文地址:Caching best practices & max-age gotchascss

譯文開始:

正確的使用緩存能夠帶來巨大的性能提高,節省帶寬,減小服務器消耗,可是不少網站對他們的緩存並無好好管理,致使相互依賴的資源不一樣步(後面會介紹)。web

緩存的最佳實踐大多數狀況下是下面兩種模式的其中一種:瀏覽器

模式一:內容不會變化 + max-age 時間設置大

Cache-Control: max-age=31536000
複製代碼
  • 這個url請求的資源內容不會改變,所以...
  • 瀏覽器/CDN能夠將這個資源緩存一年都不會有任何問題
  • 緩存資源的時間比max-age的時間小的話就能夠不諮詢服務器直接使用

在這個模式中,一些特殊的url內容永遠不會發生改變,若是內容改變你能夠修改url:緩存

<script src="/script-f93bca2c.js"></script>
<link rel="stylesheet" href="/styles-a837cb1e.css">
<img src="/cats-0e9a2ef4.jpg" alt="…">
複製代碼

寫url的地址隨着內容的變化而變化.他可使版本號,修改時間,或者內容的hash。安全

不少框架有一些工具來生成必定規則的url。bash

可是,這個模式不適用一些相似文章或者博客的內容.這些url不能被版本化,他的內容會頻繁的改變。服務器

模式2:可能會修改的內容,每次都須要服務器驗證

Cache-Control: no-cache
複製代碼
  • 這些url的資源內容可能會發生改變,所以...
  • 全部本地緩存在沒有服務器確認的狀況下都是不能被使用的

注意:no-cache不表明"別緩存",他表示在使用緩存資源以前必需要通過服務器的驗證.no-store是告訴瀏覽器別緩存資源。另外must-revalidate不是"必須通過驗證"的意思,他的意思是:本地緩存的時間若是比max-age小就能夠直接用,否則的話要通過服務器驗證。網絡

在這個模式中你能夠在響應頭裏添加ETag(一個版本id)或者Last-Modified.下次瀏覽器獲取資源的,他會經過If-None-Match把版本號帶給服務器或者經過If-Modified-Since把最後修改時間帶給服務器,容許服務器經過HTTP 304告訴瀏覽器:就用你如今有的資源,這個是最新的.或者從新返回最新的資源文件。框架

這個模式每次都會發送請求,因此他沒法像第一種模式同樣完繞過網絡請求這一步。dom

不少網站沒法知足第一種模式要求資源的內容不能變,又不想要第二種模式每次都須要發送請求。而是選擇中間的一種方式:一個很小的max-age來配合會變化的內容.這是一個很是不明智的妥協。

在內容可能會變化的資源上加上max-age一般是一個錯誤的選擇

很不幸,這種狀況並很多見,好比Github的頁面就有這樣的狀況.

想象一下:

  • /article/
  • /styles.css
  • /script.js 這些資源服務器都包含了這個響應頭
Cache-Control: must-revalidate, max-age=600
複製代碼

接下來

  • 這些url資源文件的內容發生了改變
  • 若是瀏覽器緩存的版本小於10分鐘,那麼緩存就不須要服務器驗證就能夠直接使用這些資源文件
  • 否則的話就發送一個網絡請求,同時會帶着If-Modified-Since或者If-None-Match的請求頭

這個模式在測試的時候不會有問題,可是在真實環境裏就會有問題,同時這種問題很難被定位.就好像上面這個例子,事實上服務器須要同時更新HTML,CSS和JS,可是瀏覽器從緩存中獲取了老的HTML和JS,從服務器獲取了新的CSS.版本的不統一致使了問題的發生。

一般來講.咱們更改了HTML的同時,也會同時修改CSS來裝飾你的HTML結構.也許也會同時更改JS。這些資源是相互關聯的,可是咱們的緩存頭並有反應出這些.用戶可能會同時得到一兩個新版本的資源和一箇舊版本的資源。

max-age是和響應時間有關係的,因此若是上面這些資源在一個頁面中被請求那麼他們的過時時間粗略的算式同樣的,可是仍然有可能他們的響應時間是有出入的.若是你有些頁面只包含了三個相關聯資源中的兩個,那麼他們的過時時間就會出現不一樣步的狀況。更糟的是,瀏覽器緩存常常會丟失緩存,而且緩存並不知道這三個資源是相互關聯的,那麼緩存根本不會在乎其中一個資源丟失。這些可能性加到一塊兒,緩存版本不一樣步的狀況會頗有可能發生了。

對用戶來講,結果就是破壞layout或者同時破壞交互。這些隱藏小故障,可能會致使這個頁面不可用。

幸虧,咱們還有辦法能夠拯救一下咱們的用戶...

service worker

假設你有下面這些service worker:

const version = '2';

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(`static-${version}`)
      .then(cache => cache.addAll([
        '/styles.css',
        '/script.js'
      ]))
  );
});

self.addEventListener('activate', event => {
  // 刪除老的緩存
});

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => response || fetch(event.request))
  );
});
複製代碼

這個service worker作這幾件事

  • 緩存script和styles
  • 若是匹配的話返回緩存,否則的話就發送請求

若是咱們修改了咱們的JS或者CSS,就修改version,使service workerc觸發更新。然而,由於addAll請求仍是會經過HTTP緩存(就像大多數的請求同樣),咱們任然須要面對max-age不一致致使的JS和CSS緩存不兼容的狀況.

一旦這些資源被緩存,service worker在下次更新前都將提供不兼容的JS,CSS。咱們還要祈禱下次更新的時候不會出現不兼容的狀況.

咱們可讓service worker繞過緩存:

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(`static-${version}`)
      .then(cache => cache.addAll([
        new Request('/styles.css', { cache: 'no-cache' }),
        new Request('/script.js', { cache: 'no-cache' })
      ]))
  );
});
複製代碼

可是不幸的是這個緩存選項在Chrome/Opera都不支持,只有在較新版本的Firefox支持,可是也能夠用下面的兼容方案

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(`static-${version}`)
      .then(cache => Promise.all(
        [
          '/styles.css',
          '/script.js'
        ].map(url => {
          // 使用隨機數做爲參數使他不使用緩存
          return fetch(`${url}?${Math.random()}`).then(response => {
            if (!response.ok) throw Error('Not ok');
            return cache.put(url, response);
          })
        })
      ))
  );
});
複製代碼

上面的代碼咱們經過隨機數"繞過"了緩存,可是還能夠作的更好一些,使用內容的hash來替代隨機數。就有點像經過js從新實現了模式一,可是隻能適用於能使用service worker的用戶,而不是所有的瀏覽器或者CDN。

service worker和HTTP緩存合做

你能夠在service worker裏處理緩存,可是咱們最好仍是可以找到問題的根源而且解決。正確的使用緩存會讓事情變的更簡單,不只僅是對於service worker來講,同時對那些不支持service worker的瀏覽器也有好處,而且能充分的利用CDN。

正確的使用緩存頭也意味着咱們能夠大大的簡化service worker的更新工做:

const version = '23';

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(`static-${version}`)
      .then(cache => cache.addAll([
        '/',
        '/script-f93bca2c.js',
        '/styles-a837cb1e.css',
        '/cats-0e9a2ef4.jpg'
      ]))
  );
});
複製代碼

咱們這裏給根目錄使用模式二(服務器驗證緩存有效性),剩下的資源文件使用模式一(不會改變的內容).每次service worker更新會出發一次根目錄文件的請求,可是剩餘的文件只會在url改變的時候纔會有網絡請求。不論你的緩存版本怎麼改變,都這樣能夠節省帶寬,提升性能。

這相比本來的緩存來講是一個巨大的提高,本來一個微小的改變都須要把資源整個下載。如今咱們能夠下載一個相對比較小的資源文件,來更新一個大的web。

Service workers的使用能夠做爲一個緩存的增強,而不是一個替代緩存的解決方案。因此相比於緩存對着幹,讓他和緩存一塊兒工做會帶來更好的效果。

當心使用的話,max-age配合會變的資源仍是有好處的

在有可能會改變的資源上使用max-age一般不是一個好主意,可是這個也不是絕對的.好比這個網站咱們有些會改變的資源有三分鐘的max-age,由於這個頁面沒有任何相互依賴的資源文件使用相同的緩存模式(CSS,JS還有圖片的URL使用的是模式一 - 不改變的內容),相互沒有依賴關係的資源使用同一個模式。

這意味着,若是我夠幸運能寫出一篇很是流行的文章,個人CDN能夠減輕我服務器的壓力,可是我文章的更新可能會最長鬚要延遲三分鐘才能被用戶看到,這對於我來講也是能夠接受的。

若是我在文章中新增了一個在別的文章中須要跳轉過來引用的小節,我就建立了可能會致使不同的相互關聯的資源。用戶可能點擊了連接跳轉過來發現並無發現新加的一小節內容.若是我想避免這樣的事情發生,我須要更新第一個文章,更新CDN,等待三分鐘,而後在另一個文章中加上連接.用這個模式要十分的當心。

正確的使用緩存能夠代碼巨大的性能提高,還能夠節省帶寬.對於不會改變的內容使用第一種模式,不然使用服務器驗證是比較安全的.只有在你很是有把握的時候把max-age和會改變的資源文件放在一塊兒使用,可是你要確保你的資源文件沒有相互關聯性,或者相互關聯的資源文件不會不一樣步。

相關文章
相關標籤/搜索