極限加速!Web 開發者不能不知道的 Cache 大補帖!

莫力全 Kyle Mo
14 min readMar 16, 2022

--

對於 Web 應用程式來說,「效能」是影響產品發展非常重要的關鍵因素之一,因為大部分的使用者都沒有足夠的耐心去忍受一個慢吞吞或者十分卡頓的應用,因此效能其實直接影響著產品帶來的營收與影響力,打造一個「高效能」的應用成為每個產品在開發時的基本要求與目標。

效能優化的技巧有百百種,那麼哪一個會是最多人腦海中第一個想到的技巧呢?我想許多人腦中第一個浮現的解法可能就是「Cache 快取」。然而快取這個概念其實可以實現於軟體開發的各個層面,例如比較底層的系統開發或是比較高層級的應用端開發都會運用到快取機制。那麼如果身為一個 Web 工程師,有哪些快取機制是一定需要了解的呢?本文將站在 Web 開發者的角度出發,彙整 Web 開發中從前端到後端的各種快取機制,讓讀者對於 Web 開發相關的快取機制有更全面的認識。

什麼是快取 (Cache) ?

首先讓我們先思考一個的問題,如果一直去發出網路請求或是 DB query 會造成性能影響的話,你想到最直覺的解決方式會是什麼?

那就盡量不要發出網路請求或 DB query啊!

咦,講幹話嗎,怎麼聽起來跟「吃飯會花錢怎麼辦?那就不要吃飯啊!」一樣無理。不過看似無濟於事的一個方式,卻是快取的核心概念。快取的概念其實就是提供一個額外的儲存空間,將可能需要透過請求得到的資料放在裡面,當之後要再請求資料時,先別急著發出請求,先問問快取它有沒有你要的資料吧,有的話很好,那你資料直接跟快取拿就好,也就省略了真的發出 request 的步驟,取得資料的速度也理所當然會提升,如果快取沒有你要的資料,再發出 request 去取得。而通常適合被快取的資料有兩項特性:

  • 頻繁被使用到
  • 資料不常變動

由上圖也可以得知使用快取的好處除了回應速度比較快之外,還有減輕網路頻寬、減緩 server 流量壓力…等等。

Cache Hit vs Cache Miss

一般在談快取時,Cache Hit 與 Cache Miss 是蠻常出現的名詞,所謂 Cache Hit 指的就是當發出請求時在快取就找到想要的資源,Cache Miss 指的就是快取中找不到想要的資源,必須再回去跟 origin server 拿資料。以效能優化的角度來說,會希望能夠盡量提高 Cache Hit 的比率,也就是盡量降低 Cache Miss 的比率,不過同時也要注意資料本身是不是過期了,假設今天一個電商網站的某個產品已經改了圖片上的價錢,但為了追求效能,盡量讓所有請求都可以 Cache Hit,使用者因此拿到舊的圖片,圖片上的價錢還是舊的,這就會產生許多問題了呢!所以根據需求讓現有快取失效並更新快取也是必須考量的問題。

快取的種類

其實快取一開始出現時是在指 OS 方面的機制,透過快取 ,CPU 可以不必一直到 main memory 去拿資料,從而減少性能的耗損,後來這個概念被運用到了 OS 層以外的地方,本文的重點就將放在 Web 應用端的快取機制,那麼在 Web 的前端與後端又個別有哪些 Cache 機制與策略呢?

Public vs Private

凌駕在所有快取種類之上還可以再分成公有快取與私有快取兩大類,公有快取的定義是指快取伺服器上存的回覆能給好幾個不同的請求者服務,例如請求回傳時經過的 proxy server 上的快取,這個快取上的資料可以提供給多個使用者使用。而私有伺服器相對只會服務一個請求者,例如今天主要會聚焦的瀏覽器的快取,只有在使用這個瀏覽器,也就是這台電腦的使用者可以使用快取的資源。

前端相關

  • Disk Cache (HTTP Cache)
  • Memory Cache
  • Service Worker Cache
  • CDN Cache
  • Stale While Revalidate

後端相關

  • Application cache (server side cache)
  • DB side cache
  • Cache policy

在接下來的篇章會快速帶過這些快取的基本概念,並會附上延伸閱讀的連結,想深入了解的讀者再自行研究囉!

前端相關快取機制

Disk Cache (HTTP Cache)

不知道各位讀者在瀏覽網頁時有沒有觀察過一個行為:通常第一次瀏覽頁面時畫面會花比較久的時間載入,而之後重新整理或重新造訪同樣頁面時載入速度會變快許多,這其實就是拜瀏覽器的快取所賜,你可以隨便開啟一個網站,重新整理後打開 Devtool 的 network tab,會看到許多資源會顯示 from disk cache,這表示這些資源來自瀏覽器的快取,並沒有再發出 request,因此載入速度會比第一次來的快。

HTTP Cache 是避免瀏覽器向伺服器發送不必要請求的一道防線,要啟用 HTTP Cache 需要伺服器端與瀏覽器端事先經過協商,至於協商的方式顧名思義就是透過 HTTP Request 來達成,瀏覽器與伺服器透過在 HTTP Request 與 Response 的 header 帶入一些資訊來協商快取的機制,例如伺服器告訴瀏覽器需不需要快取這個回傳的資源,或是判斷現在快取的資料是不是已經過期需要重新到 server 抓取…等等。

至於常見用來設定快取的相關 HTTP header 有:

  • Expires
  • Cache-Control
  • Last-Modified
  • If-Modified-Since
  • Etag
  • If-None-Match

整體流程大致上會是這樣子

推薦延伸閱讀:

Memory Cache

如果你是 Chrome 瀏覽器的愛好者,可能曾經發現除了 from disk cache 以外,偶爾還會出現一些資源是顯示 from memory cache,memory cache 又是什麼呢?

memory cahce 並不是所有瀏覽器都會實作的一種快取,這邊可以看作是 Chrome 特別實作的一種快取(其他瀏覽器我就不清楚了),顧名思義它把資源存在 memory (RAM) 裡面,所以在效能上會比 disk cache 還要快,但缺點是存在裡面的資料具有揮發性,當關閉瀏覽器時,原本存在 memory 的資料就會被清空。

至於瀏覽器怎麼決定哪些資源要放到 memory cache 裡,目前沒有明確的定義,只知道透過 resource hint 例如 preload, prefetch 載入的資源比較有機會被放到 memory cache 中。你可能會覺得,既然速度那麼快,就把全部資源都先存到 memory cache 就好啦?這是不可能的,記憶體的容量相比硬碟小非常多,如何有效運用 memory 的空間是很難的問題,所幸這塊身為開發者的我們不必擔心,瀏覽器都幫我們做好了。

需要特別注意的是,memory cache 在快取資源時不會管 HTTP header Cache-Control 的設定,同時在識別時也不像前面介紹的 HTTP Caching 是用 URL 或檔名來判斷,而會另外判斷 Content-Type, CORS 等其他特徵。

Service Worker Cache

Service Workers 嚴格來說屬於 Web Workers 的一種,我們都知道開發時所撰寫的 JavaScript 是運行在 Main Thread(或稱 UI Thread)上的,Service Workers 則是運行在不同於 Main Thread 的 thread 上,因此執行可以不被畫面的渲染或運算 block 住,並且具有可以在瀏覽器關閉時繼續在背景執行的能力。

而 Service Workers 又跟一般的 Web Workers 不太ㄧ樣,它是一層在瀏覽器與 network 層級之間的 proxy,擁有攔截使用者發出請求的能力(透過監聽 fetch 事件),另外 Service Workers 也提供了快取的功能,可以在攔截使用者發出的請求後決定要不要回傳快取的內容。

Service Workers 的 Cache 的優先級別是高於前面提到的 HTTP Cache 的,如果排除掉由各家瀏覽器自己實作且沒有明確規範的 memory cache 的話,Service Workers Cache 就是前端開發者可控範圍內快取的第一道防線。

有了 Service Workers 的幫助,網頁應用可以做到許多以往做不到或是不容易實現的功能,例如:

  • 透過 Service Workers 的 Cache 實現離線瀏覽功能
  • 像 Native App 一樣的推播功能
  • Background Sync

這些也是實作一個 PWA (Progressive Web App) 應用的基礎。

推薦延伸閱讀:

CDN Cache

CDN 的全名為 Content Delivery Network 內容傳遞網路。

要知道距離不僅僅是愛情的毒藥(誤),也是影響 response time 的重大因素。假設你身在台灣,跟一個架設在台灣的 server 取資料,花費的時間只要 500 ms,但如果去跟一個架設在美國的 server 取相同的資料,這時候的 response time 可能就增長為 3000 ms。

CDN 就是透過在各個地理位置建立 edge server 來避免取資源時都要跟距離遙遠的 server 溝通,造成效能的低落。當使用者對被 CDN 加速過的域名發出 request 時,CDN 會自動將 request 導到地理位置離使用者較近或是流量較不吃緊的 edge server,儘管第一次取資源時因為 CDN 還沒有快取的資料,所以仍然需要跟 original server 要資料,不過之後的 request 就可以透過地理位置離使用者較近的 CDN cache 取得,加快 client 端資源載入的速度。除了 cahce 機制以外,CDN 某方面也算是增強了服務的可用性、負載功能、安全性(降低 DDOS 對網站的影響)。

CDN 適合放一些長時間不會變動的資源,例如圖片、影音串流或是一些 CSS 與 JS 檔案,甚至是整個靜態網站也可以放。當然會變動的資源也可以放,不過既然改了就要更新快取,以避免使用者仍然拿到舊的資料,所以如果是太常改變或者是動態生成的資源就不太適合放到 CDN 上。

推薦延伸閱讀:

Stale-While-Revalidate

stale-while-revalidate 其實是一種快取的「策略」,源自於 HTTP Cache-Control header 的一個屬性

Cache-Control: max-age=1, stale-while-revalidate=59<秒數>

它的主要概念為:當第一次發出 request 時,瀏覽器會將回傳的資料存到快取裡,當之後又有相同的 request 時,瀏覽器會優先返回快取的版本,讓使用者可以迅速得到資料或是看到畫面(使用者體驗 ++),並在 background 驗證快取的資料是不是已經過期,如果需要更新就會抓取最新資料並更新快取,當下次又有請求時就可以拿到剛剛更新過並存到快取的資料。

以上面的例子來說,在第一次請求後,在 1 秒內的其他請求都會直接從快取取得資料,不會做任何 revalidation,而在 1–60 秒的區間如果有請求發生,除了回傳快取版本以外,還會在「background」去 revalidate 快取的資料,並在資料有更動時更新快取。而如果是超過 1 分鐘後的請求,就會直接以同步的方式發起網路請求抓取最新資料。

如果你曾經接觸過 react-query 或是 SWR 等套件,應該會知道它們其實就是實現 stale-while-revalidate 來做 data fetching 的管理,實踐方式是在程式 memory 中管理 cache。

推薦延伸閱讀:

Client Side 各種快取的執行順序

先前的篇章中主要介紹了 4 種 Client Side Cache

那你知道各種 Cache 的優先層級嗎?

公佈答案!

1. Memory Cache
2. Service Worker Cache
3. Disk Cache (HTTP Cache)
4. CDN Cache

推薦延伸閱讀

後端相關快取機制

Application cache (server side cache)

Application cache 也可以看成是 server side 的快取,快取存在的位置在 backend server 與 database 之間。在 web 2.0 以後,網頁內容大多是動態產生,因此資料庫的 I/O 操作也變得頻繁,I/O 的壓力也變成一個需要仔細注意的問題。採用 server side cache 在特定狀況下是可以解決這個問題的,其概念跟其他 cache 是一樣的,只是對象改為資料庫。當後端 server 要對其他伺服器發 request 取資料或是去資料庫取資料時,若是快取有資料且還沒到期就直接拿,沒有資料才做原本的 request 行為。知名的 Redis 就常被拿來當做 server side 的快取,以分擔資料庫的 I/O 壓力。

Why Redis ?

redis 本身是一個 open source 的 in memory 的 key-value資料庫,他的優點在於效能超高,並且資料結構簡單,但也因為他的 in memory 特性,所以若是 redis 掛掉的話就會有 data loss 的問題產生,所以適合用在資料掉了也沒關係的場景,也就是 cache data。

DB side cache

Cache 其實是一項概念,這個概念也可以在 database 裡面實作,例如 Materialized View (實體化檢視表),詳細內容可以參考推薦連結。

推薦延伸閱讀

Cache policy

Server Cache 的策略看似很簡單,當請求來時 Application Server 先到 Cache 看看有沒有資料,有資料的話就直接從快取拿資料,快取沒有的話再去跟資料庫拿資料。不過其實這個過程充滿了難題。

那難題出在哪?為什麼會需要不同的快取策略?在分散式系統架構且面對高流量併發請求下,我們知道 DB 會有資料不一致的問題,而同樣的,快取伺服器也需要面對一致性的難題:

在高併發的流量下,有可能產生快取與 DB 資料不一致的問題。

為了最大化快取的效益值,我們必須依照不同情況採取不同的快取策略,這是非常重要的關鍵,如果選錯了快取策略,連帶的可能造成應用效能比未使用快取時還要糟糕,資料庫也有可能負荷不了高流量,最終導致慘重的損失。

要選擇適合的 Cache Policy,發揮快取的最大效益,可以從四個面向下手:

  • 資料的種類
  • 快取的位置
  • 快取的讀流程
  • 快取的寫流程

至於到底有哪些 Cache Policy 以及它們各自的優缺點,可以參考推薦連結。

推薦延伸閱讀

總結

提到效能優化,Cache 可以說是經典的一個機制,其實嚴格來說它只是一個「概念」,基本上在軟體開發的任何層級都有可能可以實作這個概念。今天這篇文章大略介紹了 Web 前端到後端的相關快取機制,強烈建議讀者可以循著推薦連結去更深入的了解各種快取機制,有了更深的掌握以後,身為 Web 開發者的你一定會具備足夠的知識來幫你的網站極限加速!

--

--

莫力全 Kyle Mo
莫力全 Kyle Mo

Written by 莫力全 Kyle Mo

什麼都想學的雜食性軟體工程師 🇹🇼 (https://github.com/kylemocode) 合作與聯繫 📪 oldmo860617@gmail.com IG 技術自媒體:@kylemo.webdev.life

Responses (2)