快取機制 (Caching)
一句話定位
快取是讀取太慢或太貴時的解法。把熱資料放在快速記憶體裡,讓大多數讀取直接跳過資料庫。
取捨核心:速度 vs 過時 / 失效 / 故障。
為什麼需要快取
| 場景 | 沒快取 | 有快取 |
|---|---|---|
| 讀取用戶資料 | PostgreSQL ≈ 50ms | Redis ≈ 1ms(50×) |
| 個人化 feed(多 join) | 200ms | 1ms |
| 高峰 DB CPU | 80% | 降 70–80% |
資料庫存在磁碟、查詢付磁碟代價;記憶體離 CPU 近得多。
在哪裡快取(四層)
1. 外部快取(External Cache) — 預設答案
獨立服務,透過網路存取(Redis / Memcached)。
- ✅ 所有 app server 共用,擴展性好
- ✅ 支援 LRU、TTL,記憶體可控
- 💡 面試預設:高流量系統提到快取,就是 Redis
2. CDN — 靜態媒體首選
地理上分散,把內容快取在靠近用戶的 edge server。
維吉尼亞 origin → 印度用戶:250–300ms
CDN edge → 同區域用戶:20–40ms
- ✅ 最穩妥的引入理由:大規模 靜態媒體(.png/.jpg/.svg)
- 現代 CDN 也能快取 API 回應 / 動態內容 / edge logic,但面試從靜態出發較不會被追問
3. 客戶端快取(Client-Side)
- 瀏覽器 HTTP cache、localStorage
- 行動 App 的裝置儲存
- Library 內部 metadata cache(如 Redis client 快取 cluster slot 配置)
- ⚠️ 後端能控制的有限、失效難處理
4. 行程內快取(In-Process)
直接在 application 行程的記憶體裡。
- ✅ 比 Redis 還快(無網路 hop)
- ❌ 每個 instance 各自一份,不共享;其中一台失效,其他不知道
- 適合:feature flag、設定值、小型參考集、熱 key、rate limiter 計數
- 💡 不是 Redis 的替代品,是額外優化層。面試先講外部快取再提它
四種快取架構模式
| 模式 | 寫入端 | 讀取端 | 適用 |
|---|---|---|---|
| Cache-Aside | App 直寫 DB,delete 或 update cache | App 先查 cache,miss 就查 DB 再 set cache | 預設首選 |
| Write-Through | App 寫 cache → cache 同步寫 DB | 同 cache-aside | 讀必新鮮、可接受寫變慢 |
| Write-Behind | App 寫 cache → 背景非同步 flush DB | 同上 | 高寫入吞吐、可接受最終一致 |
| Read-Through | 同 Write-Through | cache 自己處理 miss | CDN 本質就是這種 |
Cache-Aside(旁載快取)— 預設
讀取:app → cache?
hit → return
miss → app → DB → set cache → return
寫入:app → DB
app → cache.delete(key) ← 失效,不更新
如果你只記一種模式,就記 Cache-Aside。
它和語言、框架無關,所有快取都能實作。
Write-Through / Write-Behind / Read-Through
- Write-Through:寫變慢但讀永遠新鮮;仍有 dual-write 問題(cache 成功、DB 失敗,或反之)
- Write-Behind:寫超快,但 cache 崩潰前未 flush 的資料會遺失。常見於分析 / metrics pipeline
- Read-Through:cache 充當智慧代理,app 不直連 DB。CDN 本質如此。對 application 層的 Redis 較少見
淘汰策略(Eviction Policy)
| 策略 | 規則 | 適用 |
|---|---|---|
| LRU | 移除最久沒被存取的 | 預設首選(90% 工作負載適合) |
| LFU | 移除存取次數最少的 | 長期持續熱門(熱門影片、排行榜) |
| FIFO | 移除最早進入的(不看存取) | 極少用,可能踢掉熱資料 |
| TTL | 不是淘汰策略,是每個 key 的過期時間 | 必備,常和 LRU/LFU 搭配 |
常見快取問題(面試 deep dive 區)
Cache Stampede(快取雪崩 / Thundering Herd)
熱門 key 過期瞬間,大量請求同時 miss 直接打 DB。
12:01:00 整點 key 過期
→ 數千請求瞬間 miss
→ 同時打 DB
→ DB 過載連鎖故障
解法:
- Request Coalescing(請求合併 / Single Flight) — 最有效
只讓一個請求去重建快取,其他等它的結果 - Cache Warming — 熱門 key 過期前主動刷新
⚠️ 只在 TTL 過期模式有效;寫入失效模式無效
Cache Consistency(快取一致性)
最常被問。發生在 cache / DB 對同一資料回不同值時。
用戶改大頭照 → 寫進 DB → 舊值還在 cache → 其他用戶在 TTL 內看到舊大頭照
解法(根據資料新鮮度選):
- 寫入時失效(最常用):
UPDATE db; cache.delete(key) - 短 TTL:可接受短暫不一致就用 TTL,到時自然刷
- 接受最終一致性:feed、metrics、analytics 的短暫延遲通常沒差
Hot Keys(熱 key)
單一 key 流量遠超其他,把某個 Redis 節點打掛。
user:taylorswift每秒幾百萬請求,整個 Twitter 個人資料瓶頸在一台 Redis
解法:
- 複製熱 key 到多節點 — 分散讀負載
⚠️ 各副本 TTL 錯開,否則同時過期再觸發 Stampede - 加行程內備援快取 — 極端熱門值不要每次都打 Redis
- Rate Limiting — 對特定 key 異常流量踩煞車
面試怎麼談快取(五步驟)
不要一上來就喊 cache —— 先確立為什麼需要
Step 1:確認瓶頸
「用戶個人資料查詢尖峰每秒 500 次,每查 30ms,這是瓶頸。」
Step 2:決定快取什麼
- 讀取頻繁
- 不常變動
- 取得成本昂貴
設計 key:user:123:profile、trending:posts:global
Step 3:選快取架構
「我用 cache-aside。讀取先查 Redis,miss 就查 DB 並回填,再回傳。」
靜態內容 → 加 CDN。極端熱 key → 加 in-process。
Step 4:設定淘汰策略
「Redis LRU + user profile 10 分鐘 TTL。用戶更新時立刻 invalidate。」
Step 5:說明缺點(取捨)
挑 1–2 個最相關的:
- 失效:寫入時 invalidate / TTL / 接受最終一致
- 故障:Redis 掛了 → 降級直連 DB + circuit breaker,避免雪崩壓垮 DB
- Stampede:probabilistic early expiration / request coalescing
不要快取所有東西
Senior+ 面試官在意你知道何時不該快取。一個 index 設計良好的 DB 已經夠用的場景,不用硬加 cache。
自我測驗重點
| 問題 | 重點 |
|---|---|
| Latency 排序 | In-Process < Redis < CDN |
| Cache-Aside vs Write-Through 差異 | 誰主動寫 cache、寫入時序 |
| Cache Stampede 最有效解 | Request Coalescing |
| Eviction policy 錯誤敘述 | FIFO 不是生產主流 |
| Hot Key 解法 | 複製 / in-process / rate limit |
| 五步驟 | 瓶頸 → 快取什麼 → 架構 → 淘汰 → 缺點 |
Related Notes
- 02-Distributed-Systems/05-Numbers-to-Know — Redis 1ms、100k+ ops/sec 量級
- 02-Distributed-Systems/04-Consistent-Hashing — 熱 key 解法(複製、virtual node)
- 01-Networking/06-Load-Balancing — CDN 屬於負載 / 加速範疇
- 05-Database-Advanced/03-Replication — read replica 是 cache 之外的讀取擴展