剛開始寫網頁時,總是會遇到改了檔案重新整理畫面卻沒更新,或是更新了一張圖檔到伺服器但網頁內容沒有更換等情況,使得現在按 Ctrl/CMD + R 時,小指都會自動同時按下 Shift 來忽略快取。
但到底什麼是快取?
快取是什麼#
想像一下,假設有個人叫做小明,他每天都會照三餐滑 Instagram 配飯吃,可能一下子就看個好幾十張照片。又因為小明沒有朋友,所以很容易就滑到重複的內容了。
小明在滑的過程中,其實就等同於不斷地向 Instagram 拿到下一則動態。但如果這則動態是小明先前看過的,又重複向伺服器拿取,一來一往之間就浪費了不少網路傳輸量。
快取就是幫我們省下這個浪費,將你看過的東西留下來,在需要時便能直接拿出來用,不用再次向資料來源端請求。
如果使用者已經造訪過該網站,其實有不少資源都可以重複利用,例如不會變動的靜態資料、頁面中的部分圖檔等等。這些資源便可以透過一層層的快取機制,將內容留存住。
快取的分層#
既然快取就是將內容留存一份,方便下次取用,那麼應該留在哪裡?
從網站使用的方向來思考。首先是使用者的電腦及瀏覽器,在送出請求前,會先確認是否有本地快取(也就是清除快取在清除的東西),如果沒有或是快取過期,瀏覽器才會真正送出請求。
除此之外,透過 JavaScript 操作 Web Storage API,也可以做為快取的儲存層。接下來,拿的資源如果被放在 CDN,那麼就會連到最近的 CDN 節點取得資源;這些 CDN 節點對於伺服器來說,也算是一種內容快取。
另外,在請求傳遞到伺服器的過程中,可能會經過路由、代理伺服器、負載平衡器等設備,這些也都是可以暫存資料的地方。最後請求真的到伺服器時,伺服器如果需要向其他外部服務取得資源如 DB、API 等等,也可以透過快取的方式來儲存常用資料。
一切的目標就只有一個:重複利用資源以避免浪費效能。
網頁的快取機制#
接下來就聚焦在與前端較為相關的本地 HTTP 快取(HTTP Cache)。如同跨域問題,關於快取的設定也大都是由伺服器設定 Header 來進行控制。
Cache-Control#
最常見的大概是 Cache-Control 了,它有四個狀態:
public:公開的資源,可以被所有節點暫存private:私有的資源,只被允許儲存成使用者的本地快取no-cache:本地暫存,但使用前會先詢問過期沒no-store:不開快取
其中,public 及 private 還可以設定 max-age=... 來指定快取多久後會過期。
例如 Cache-Control: private max-age=2592000,描述的是此資源僅可以被本地快取,有效期限是 30 天(60 x 60 x 24 x 30 = 2592000)。
還有一個功能類似的設定
Expires,算是歷史產物。但根據規範所述,Expires的值會被max-age=...蓋掉,暫時可以不用理它。
這種透過 Cache-Control 與 Expires 直接控制快取過期時間的機制,稱為強快取(Strong Cache)。
ETag#
定義了快取過期時間,但過期了不見得就不能用。如果檔案沒有更改過,是不是就不用重新下載資源?
這就是 ETag 屬性的用途,也是協商快取(Conditional Cache)的核心機制。
開發者可以在 Header 設定 ETag,值為檔案計算後的 hash 值。由於同一份檔案由同一個伺服器算出來的 ETag 會完全相同,當使用者快取過期,在重新請求資源時便會帶著 ETag,伺服器重新計算後,如果 ETag 值沒有變動,表示檔案沒有更動,將回傳 304,表示檔案未更動,可以直接繼續使用。
檔名控制#
前面提到,可以藉由 max-age=... 來指定快取的有效期限。但如果今天某資源在有效期限內有更動,前述的快取機制便會忽略掉這個更動,導致資源版本沒有同步。
這樣的問題,可以在程式的檔名加上類似 ETag 的 hash 值,當版本更新、檔案名稱更動,瀏覽器自然也會忽略快取,直接向新資源發送請求。
但總不可能每次修改都手動改檔名,這時候就需要打包工具。例如最熱門的 Webpack,只需要在 output filename 的地方設定 contenthash,就可以快速地加上 hash 值:
// webpack.config.js
module.exports = {
entry: "./src/index.js",
plugins: [
new HtmlWebpackPlugin({
title: "Output Management",
title: "Caching",
}),
],
output: {
filename: "[name].[contenthash].js",
path: path.resolve(__dirname, "dist"),
},
};如此一來,藉由 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 的設定,便可以將大部分的快取功能設置完成;如果真的有意外的更動,也可以透過打包工具變動檔名,讓使用者直接請求新資源。
原文出處#
- 原書/iThome:https://ithelp.ithome.com.tw/articles/10226607