網頁快取是什麼?Web cache 機制是怎麼運作的?

筆者在剛開始寫網頁時,總是會遇到改了檔案重新整理畫面卻沒更新,或是更新了一張圖檔到伺服器但網頁內容沒有更換等情況,使得現在按 Ctrl/CMD + R 時,小指都會自動同時按下 Shift 來忽略快取;相信不少人多少都有類似的經驗吧?但到底什麼是快取呢?

快取是什麼

想像一下,假設有個人叫做小明,他每天都會照三餐滑 Instagram 配飯吃,滑滑按按,可能一下子就看個好幾十張照片,就先算他個 50 張好了;又因為小明沒有朋友,所以很容易就滑到重複的內容了,可能看的 50 張裡有一半是看過的舊的照片。

小明在滑的過程中,其實就等同於不斷地向 Instagram 拿到下一則動態,但如果這則動態是小明先前看過的,如果又重複向伺服器拿取,一來一往之間就浪費了不少網路傳輸量;快取就是幫我們省下這個浪費,將你看過的東西留下來,在需要時便能直接拿出來用,不用再次向資料來源端請求。

幾天前 我們聊到了網站優化,文中提到了全世界網站內容的資源量不斷增加,現在(2019/09)網站的平均資源下載量已經到達 5M 之譜;但如果使用者已經造訪過該網站,其實有不少資源都可以重複利用對吧?例如不會變動的靜態資料、頁面中的部分圖檔等等;這些資源,便可以透過一層層的快取機制,將內容留存住。

快取的分層

既然快取就是將內容留存留存一份,方便下次取用,那麼應該留在哪裡呢?

從網站使用的方向來思考吧。首先是使用者的電腦及瀏覽器,在送出請求前,會先確認是否有本地快取(也就是我們清除快取在清除的東西),如果沒有或是快取過期,瀏覽器才會真正送出請求。

除此之外,透過 JavaScript 操作 Web Storage API,也是可以做為快取的儲存層;接下來,我們拿的資源如果被放在 CDN,那麼我們就會連到最近的 CDN 節點取得資源;這些 CDN 節點對於伺服器來說,也算是一種內容快取;另外,在請求傳遞到伺服器的過程中,可能會經過路由、代理伺服器、負載平衡器等設備,這些也都是可以暫存資料的地方;最後請求真的到伺服器時,伺服器如果需要向其他外部服務取得資源如 DB、API 等等,也可以透過快取的方式來儲存常用資料。

這麼多種快取方式,是不是有點頭大啊?其實一切的目標就只有一個:重複利用資源以避免浪費效能。例如像前述 Instagram 的例子,一個每天有超過 5 億使用者的服務,藉由妥善的快取機制,能省下多少流量資源,自然不在話下。

網頁的快取機制

說了那麼多,但今天剩下的內容就聚焦在與前端較為相關的本地快取吧!關於快取的設定也大都是由伺服器設定 Header 來進行控制。

Cache-Control

最常見的大概是 Cache-Control 了,它有四個狀態:

  • public:公開的資源,可以被所有節點暫存
  • private:私有的資源,只被允許儲存成使用者的本地快取
  • no-cache:本地暫存,但使用前會先詢問過期沒
  • no-store:不開快取

其中,publicprivate 還可以設定 max-age=… 來指定快取多久後會過期;例如 Cache-Control: private max-age=2592000,描述的是此資源僅可以被本地快取,有效期限是 30 天(60 x 60 x 24 x 30 = 2592000)

還有一個功能類似的設定:Expires,算是歷史產物吧;但根據 規範 所述,Expires 的值會被 max-age=… 蓋掉;暫時可以不用理它。

ETag

定義了快取過期時間,但如同 有些食物即使過期了仍然能吃 一樣,過期了不見得就不能用;如果檔案沒有更改過,是不是就不用重新下載資源了呢?

沒有錯,在上圖的最後有個 ETag 屬性,就是來解決這個需求的。

開發者可以在 Header 設定 ETag,值為檔案計算後的 hash 值;由於同一份檔案由同一個伺服器算出來的 ETag 會完全相同,當使用者快取過期,在重新請求資源時便會帶著 ETag,伺服器重新計算後,如果 ETag 值沒有變動,表示檔案沒有更動,將回傳 304,表示檔案未更動,可以直接繼續使用。

圖片來自 HTTP Cat <3

檔名控制

前面提到,可以藉由 max-age=… 來指定快取的有效期限對吧?但如果今天某資源在有效期限內有更動,前述的快取機制便會忽略掉這個更動,導致資源版本沒有同步。

這樣的問題,我們可以在程式的檔名加上類似 ETag 的 hash 值,當版本更新、檔案名稱更動,瀏覽器自然也會忽略快取,直接向新資源發送請求。

但總不可能每次修改都手動改檔名吧?當然,這時候就需要我們 之前說到的打包工具 了!例如最熱門的 Webpack,只需要在 output filename 的地方設定 contenthash,就可以快速的加上 hash 值囉:

如此一來,藉由 Header 及檔名的雙重控制,開發者便能完全控制使用者取得資源的快取方式了。

結語

Stack OverFlow 的共同創辦人 Jeff Atwood 曾說過這段話:

There are two hard things in computer science: cache invalidation, naming things, and off-by-one errors.

快取是優化效能的苦口良藥,控制得當能省下大量的資源,但要真的能完全控制得當就是一門大學問了;藉由 Cache-Control ETag 兩個 Header 的設定,便可以將大部分的快取功能設置完成,如果真的有意外的更動,也可以透過打包工具變動檔名,讓使用者直接請求新資源。

那麼以上就是今天的快取淺淺談啦,若讀者您有任何疑問或不清楚的地方,都歡迎於底下留言討論。

(本文授權轉載自前端三十 [FE] 網頁的快取機制是怎麼運作的? 作者是ALPHA Camp的助教 前端工程師 Gary

三分鐘小測驗,找到你學習網路開發的入口

參考資料