快取策略:什麼時候該快取?
快取的本質
沒有快取:
使用者 → API → 資料庫 → API → 使用者(每次都查 DB,100ms)
有快取:
使用者 → API → 快取命中 → 使用者(1ms)
↘ 快取沒命中 → 資料庫 → 存入快取 → 使用者
快取策略比較
Cache-Aside(旁路快取)— 最常用
public async Task<User> GetUser(int id) {
var cached = await _cache.GetAsync<User>($"user:{id}");
if (cached != null) return cached; // 快取命中
var user = await _db.Users.FindAsync(id); // 查 DB
await _cache.SetAsync($"user:{id}", user, TimeSpan.FromMinutes(10)); // 存入快取
return user;
}
優點:簡單、應用控制快取邏輯
缺點:第一次一定 miss、可能有短暫的資料不一致
Write-Through(寫穿快取)
寫入時同時更新 DB 和快取
→ 快取永遠是最新的
→ 但寫入速度變慢(要寫兩個地方)
Write-Behind(延遲寫入)
寫入先更新快取,異步再寫 DB
→ 寫入超快
→ 但有資料遺失風險(快取掛了,DB 還沒更新)
什麼時候該快取?什麼時候不該?
| ✅ 該快取 | ❌ 不該快取 |
|---|---|
| 讀多寫少(商品列表) | 即時性要求高(帳戶餘額) |
| 計算成本高(報表) | 資料頻繁變動(聊天訊息) |
| 資料不常變動(設定檔) | 個人化資料(除非 key 包含 userId) |
| 外部 API 回應 | 安全敏感資料 |
快取失效策略
TTL(Time-To-Live):設定過期時間
→ 最簡單,但過期前資料可能是舊的
主動失效:資料更新時刪除快取
→ 最精確,但要記得在所有更新點都清快取
版本號:快取 key 包含版本號
→ 資料變更時改版本號 → 舊快取自然失效
快取最難的不是「怎麼快取」,而是「怎麼失效」。
快取穿透、雪崩、擊穿
穿透:查一個不存在的 key → 每次都打 DB
→ 解法:快取空值(null 也存,TTL 短一點)
雪崩:大量快取同時過期 → DB 瞬間被打爆
→ 解法:TTL 加隨機值(不要全部同時過期)
擊穿:一個熱點 key 過期 → 大量請求同時查 DB
→ 解法:互斥鎖(只讓一個請求去查 DB,其他等待)