前端效能優化之HTTP快取策略

Codeeeee發表於2019-02-14

背景

很多時候,當開啟瀏覽器的開發者工具,檢視網路請求,對於資源大小(Size)選項,除了有具體的數字大小,還有from memory cache、from disk cache欄位之類出現。

這裡就有很多疑問,這些欄位代表著什麼意思?這些欄位又是誰來決定的?

前端效能優化之HTTP快取策略

快取位置

從字面意思理解,大概也能猜到,這些欄位代表著快取位置。 按優先順序,Size選項欄位可分為:

  • from Service Worker
  • from memory cache
  • from disk cache
  • 真正的網路請求(顯示資源的具體大小)

Service Worker

本質是作為伺服器與客戶端之間的代理伺服器,伴隨著PWA出現。Service Worker真正意義上將快取控制權交給了前端,相比於LocalStorage、SessionStorage,後兩者只是單純的介面資料快取,例如使用者資訊(一個物件)、列表資訊(一個陣列),而前者可以快取靜態資源,甚至攔截網路請求,根據網路狀況作出不同的快取策略。當然,這不是本文討論的重點。

memory cache

顧名思義,這個是將資源快取在了記憶體中。事實上,所有的網路請求都會被瀏覽器快取到記憶體中,當然,記憶體容量有限,快取不能無限存放在記憶體中,因此,註定是個短期快取。

記憶體快取的控制權在瀏覽器,前後端都不能干涉。

disk cache

與記憶體快取相對的,這個是將資源快取在硬碟中。雖然相比於記憶體,硬碟的讀取速度要慢很多,但總比沒有強。

硬碟快取的控制權在後端,通過什麼控制呢?通過HTTP響應頭控制,這是本文重點討論的。

快取策略

disk cache也叫http cahce,因為其嚴格遵守http響應頭欄位來判斷哪些資源是否要被快取,哪些資源是否已經過期。絕大多數快取都是disk cache。

disk cahce分為強制快取與對比快取。

強制快取

控制強制快取的有兩種http響應頭欄位:

Expires: Fri, 08 Feb 2019 05:37:33 GMT

欄位的值就代表了資源的過期時間,不過這個值是相對於客戶端,並且客戶端本地時間可以任意修改,因此這個欄位並不可靠。Expires欄位是Http 1.0的,Http 1.1 用Cache-Control欄位替代它:

Cache-Control: max-age=2592000

Cache-control欄位使用了絕對時間,單位為秒,即最大有效時間,在有效時間內,客戶端直接從硬碟中讀取資源。

看個例子,用Node.js搭建一個靜態資源伺服器,設定Cache-Control: max-age=2592000,每次請求都會被伺服器列印出:

const server = http.createServer((req, res) => {
    console.log(`收到請求,請求地址為: ${req.url}`);
    fs.readFile(path.resolve(__dirname, './image.png'), (err, file) => {
        if (err) {
            res.end(err.message);
        }

        res.setHeader('Cache-control', 'max-age=2592000');
        res.end(file);
    });
}).listen(3000);

console.log('localhost:3000服務已開啟!');
複製程式碼

前端效能優化之HTTP快取策略

第一次訪問:

前端效能優化之HTTP快取策略

前端效能優化之HTTP快取策略

第二次訪問:

前端效能優化之HTTP快取策略

前端效能優化之HTTP快取策略

可以看到,第一次請求,瀏覽器根據響應頭中的Cache-Control欄位,將資源快取在硬碟中,第二次請求,瀏覽器直接從硬碟中讀取資源,並沒有傳送網路請求到伺服器。

Cache-Control欄位有以下可取值:

  • max-age=xxx,最大的有效時間
  • must-revalidate,如果超過了max-age的時間,必須向伺服器傳送請求,驗證資源的有效性
  • no-cache,基本等價於max-age=0,由對比快取來決定是否快取資源
  • no-store,真正意義上的不快取
  • public,所有內容都可以被快取
  • private,所有內容只有客戶端可以快取,代理伺服器不能快取。預設值

對比快取

不同於強制快取,瀏覽器直接根據響應頭Cache-Control欄位直接判斷快取資源是否有效,對比快取需要再次向伺服器確認。

Last-Modified & If-Modified-Since

伺服器通過響應頭Last-Modified告知瀏覽器,資源最後被修改的時間:

Last-Modified: Fri, 08 Feb 2019 15:20:04 GMT

當再次請求該資源時,瀏覽器需要再次向伺服器確認,資源是否過期,其中的憑證就是請求頭If-Modified-Since欄位,值為上次請求中響應頭Last-Modified欄位的值:

If-Modified-Since: Fri, 08 Feb 2019 15:20:04 GMT

伺服器會接收If-Modified-Since欄位的值與被請求資源的最後修改時間作比較

如果If-Modified-Since的值大於被請求資源的最後修改時間,則說明瀏覽器快取的資源仍然有效,伺服器會返回304狀態碼,告知瀏覽器直接取快取即可。其中伺服器返回的只有Http頭部,並不包含主體(不然就沒有快取的意義了)。

否則,就跟正常的請求一樣,伺服器返回200狀態碼,並附帶最新的資源。

看個例子,稍微修改下剛才的Node.js程式碼:

const server = http.createServer((req, res) => {
    console.log(`收到請求,請求地址為: ${req.url}`);

    const filename = path.resolve(__dirname, './image.png');

    fs.stat(filename, (err, stat) => {
        const lastModified = stat.mtime.toUTCString();

        if (lastModified === req.headers['if-modified-since']) {
            res.writeHead(304, 'Not Modified');
            res.end();
        }
        else {
            fs.readFile(filename, (err, file) => {
                if (err) {
                    res.end(err.message);
                }
                
                res.setHeader('Last-Modified', lastModified);
                res.end(file);
            });
        }
    });
}).listen(3000);

console.log('localhost:3000服務已開啟!');
複製程式碼

第一次請求:

前端效能優化之HTTP快取策略

第二次請求:

前端效能優化之HTTP快取策略

比對兩次請求可以看到,除了狀態碼變成了304,資源大小也從57.8K降到了90B,這也證明響應中不包含http主體。

Etag & If-None-Match

Last-Modiflied與Expires一樣,也是有缺陷的。如果,資源的變化的時間間隔小於秒級,比如說是毫秒級的,或者說資源直接是動態生成的,那根據Last-Modified判斷,資源就是每時每刻都最新的,即被修改過!

所以,Etag & If-Node-Match 就是來解決這個問題的。

Etag欄位的值為檔案的特殊標識,一般都是hash生成的,伺服器儲存著資源的Etag值。接下來的流程都與Lst-Modified & If-Modified-Since一致,只不過,比較的值從最後修改時間變成了Etag值。

Etag的優點在於,對於動態資源或者現在流行的Restful API返回的JSON資料,這些是沒有修改時間這一說法的,但是Http標準並沒有規定Etag值如何生成,因此我們通過程式碼自己生成Etag值。當然,計算Etag值會消耗伺服器效能。

優先順序

強制快取與對比快取是可以同時存在的,並且強制快取的優先順序高於對比快取。實際應用中,也是兩者共同使用的。

看個例子,在響應頭中同時加上Cache-Control與Last-Modified:

res.setHeader('Cache-control', 'max-age=600');
res.setHeader('Last-Modified', lastModified);
複製程式碼

第一次請求:

前端效能優化之HTTP快取策略

第二次請求:

前端效能優化之HTTP快取策略

可以看到,雖然有Last-Modified欄位,但還是直接從硬碟中獲取資源。

總結

Http快取策略,其實只是前端快取的一小部分,但零亂的知識點還是非常多的。最終處理快取還是瀏覽器,各瀏覽器的處理方式可能有差異,實際應用中還是要慎重考慮。

合理運用Http快取,對前端效能優化還是非常有幫助的!

相關文章