為什麼需要這個
「加快取」這句話其實沒有講清楚要加在哪一層——同一個「太慢」的問題,解法可能是 CDN、可能是 Redis、也可能只是 process 裡一個 Map。加錯層的代價:靜態圖片塞 Redis(浪費最貴的記憶體)、把該進 CDN 的 API 回應放 local cache(每台機器各存一份還不一致)。
我自己的經驗比「加錯層」更前面一步:很多團隊連自己有哪些層都畫不出來。我待過有雲有地的混合環境——request 進來第一站其實是 DNS,那裡就有一段快取,但能把 DNS 快取更新做好的公司,我的過往經驗裡沒幾間;再往下一跳,到底先碰到 WAF 還是 LB,現場沒人講得清楚,整段鏈路亂到系統之間連個 message queue 都沒有、資料真的四散各地。「加錯層」至少代表你知道層在哪,只是放錯;我看過的真實世界是連地圖都沒有。這篇就是我當時希望有人塞給我的那張地圖。
這一疊層長什麼樣
由近到遠(對 CPU 而言):
| 層 | 延遲數量級 | 容量 | 誰管它 | 攔什麼 |
|---|---|---|---|---|
| CPU cache(L1-L3) | ns | KB-MB | 硬體,你碰不到 | 熱指令與資料 |
| Process 內(Map/LRU) | ~百 ns | MB | 你的 code | 單機熱資料、設定 |
| Redis(分散式) | ms 內(含網路) | GB | 你的團隊 | 跨機共享的動態資料 |
| CDN(邊緣) | 10ms 級但省洲際往返 | TB | CDN 商 | 靜態資源、可公開快取的回應 |
每一層 miss 就掉到下一層——跟 Request 的一生 的「大部分 request 走不到最右邊」是同一個故事的快取視角。
越近越快,但代價各不同
- Process 內:最快也最危險——多台機器各一份,一致性全靠 TTL 賭運氣;適合「錯了也沒差」的資料(設定、字典表)
- Redis:共享一份所以一致性好管,但多一趟網路、而且它自己成為要維運的服務(→ 一路的 stampede/穿透/hot key 文章都在講這層)
- CDN:離使用者最近、量最大,但失效最難(invalidation 兩大難題 在這層最痛——purge 全球節點)
分工原則的一句話版本,老實說我給不出來——因為我看過的現場比「分錯層」還要前面:一台 worker 配的 Redis 壞掉了沒有人知道;cloud function 各自往某個 repo 存東西;系統之間沒有 queue;連 log 都一下有一下沒有。我說不上來怎樣是對的,但我非常確定那樣是錯的。所以我的原則是反著來的第 0 條:一層的資料誰在用、掛了誰會知道——這兩題答不出來,就不要開那一層。分工口訣是第 1 條以後的事;先讓每一層有主人、有監控,再來談哪層放什麼。
常見的層次誤用
- 靜態資源沒上 CDN、卻拿 Redis 快取 HTML 片段
- 把 request-scope 的重複查詢當 Redis 問題解(其實一個 in-request memoize 就好)
- 每台 app 各自 local cache 使用者權限 → 改權限後行為不一致被客訴
我自己看過的版本是快取立功之後的後遺症。前面說過我們靠補 Redis 救回被打掛的 DB——但立過一次功,Redis 就從「解某個問題的工具」變成「預設動作」,什麼東西都往裡塞,連訂單資料都進去了。訂單是整個系統裡最不該快取的東西:狀態一直在變(金流、物流一路更新)、寫多讀少、錯一次就是客訴——而且它又不是後台報表那種「慢一點沒關係」的讀場景,塞進快取根本換不到什麼,卻多養了一份會過期的複本。層次誤用最常見的成因不是不懂,是上一次的成功經驗被無腦複製。
何時不適合
- 層次是「攔截讀」的模型;寫多讀少的資料加任何一層都是負擔(先回去看 為什麼加 cache 更糟)
- 小流量產品可能一層都不需要——DB 加對 index 就結束了