跳到主要內容

系統設計

把資料結構放大到好幾台機器

主題 · 快取

快取

在慢的資料庫前面放一層快的記憶體。因為熱門資料通常很集中,小小的快取就能擋掉大部分的讀取。

程式碼路徑
1.0
100 (10%)
淘汰策略
命中率(s = 1.0)每個鍵一樣熱門(s = 0)
0%25%50%75%100%0%10%20%30%40%50%快取大小(佔全部 1,000 個鍵的比例)58%
每 100 次讀取
58 快取
42 資料庫
命中率
57.8%
平均延遲
4.7 ms
p99 延遲
11 ms
每千次讀取打到資料庫
422

假設快取讀一次 0.5 ms、資料庫讀一次 10 ms。對 1,000 個鍵讀 20,000 次;前 4,000 次用來暖機,不計入。

快取只放得下 10% 的鍵,卻接住了 58% 的讀取。熱度這麼集中時(s = 1.0),大家都在讀的那一小撮資料放得進小小的快取;如果每個鍵一樣熱門(s = 0),快取能接住的比例就只等於它裝得下的比例。注意 p99(第 99 百分位):命中率沒超過 99% 之前,最慢的那 1% 讀取還是得去資料庫。

亮起來的是這一步執行的程式碼
interface Database {
read(key: string): string; // slow: ~10 ms
}
class CacheAside {
// A Map remembers insertion order: first key = least recently used.
private cache = new Map<string, string>();
constructor(private capacity: number, private db: Database) {}
get(key: string): string {
const cached = this.cache.get(key);
if (cached !== undefined) {
this.cache.delete(key);
this.cache.set(key, cached);
return cached;
}
const value = this.db.read(key);
if (this.cache.size >= this.capacity) {
const oldest = this.cache.keys().next().value!;
this.cache.delete(oldest);
}
this.cache.set(key, value);
return value;
}
}

模型假設與範圍

  • 這是可重現的教學模型;延遲、容量、故障率與工作負載是設定或樣本,不能直接當作正式系統的效能承諾。
  • Cache-aside、LRU 與 Zipf 存取分布;命中和資料庫延遲是設定值。未模擬更新一致性與連線池競爭。

什麼時候用

  • 讀遠多於寫、而且熱門資料很集中時:快取只要放得下熱門的那一小撮,就能擋掉大部分讀取。
  • 資料稍微舊一點也沒關係時(商品頁、個人檔案、排行榜)。要求每次都讀到最新值的資料不適合快取。
  • 計算很貴、結果可以重複使用時:快取的不一定是資料庫的列,也可以是算好的頁面或查詢結果。

和其他主題的關係

延伸閱讀
一致性雜湊

出現在這些架構裡

時間與空間複雜度(Big O)

操作平均最差
讀取:命中O(1)O(1)
讀取:沒命中
再加上一次資料庫讀取——時間主要花在那裡,不在快取
O(1)O(1)
淘汰最久沒用的鍵O(1)O(1)

空間:O(C),C 是快取容量;有序的雜湊表本身就是雜湊表加一條串列

和其他做法比

快取 1% 的鍵快取 5% 的鍵快取 10% 的鍵快取 20% 的鍵
s = 0(一樣熱門)1%5%10%20%
s = 0.88%27%38%52%
s = 1.021%46%58%70%
s = 1.241%66%76%84%

表中是 LRU(Least Recently Used)的命中率:1,000 個鍵、熱度依 Zipf 分佈(第 k 熱門的鍵被讀的機率正比於 1/kˢ),讀 20,000 次、前 4,000 次暖機不計。一般網站流量的 s 大約在 1 附近。淘汰策略裡的 FIFO 是 First In, First Out(先進先出):滿了就丟掉最早放進來的那一筆,不管它最近有沒有被用到。

真實世界裡的它

  • Redis、Memcached 放在資料庫前面,就是這裡的 cache-aside。
  • CDN 是放在全世界各地的快取;瀏覽器快取是放在你電腦裡的快取。
  • CPU 的 L1/L2/L3(第一、二、三層)快取也是同一個道理,只是時間尺度是奈秒。

取捨與陷阱

  • 快取失效很難:資料庫更新了,快取裡的舊值什麼時候清?常見做法是寫入時刪除快取,加上 TTL(Time To Live)當保險。
  • 驚群效應:熱門的鍵一過期,成千上萬個請求同時落空、同時去打資料庫。要用鎖或提前更新擋住。
  • 平均延遲降了,p99 不一定降:只要還有超過 1% 的讀取落空,最慢的那 1% 仍然是資料庫的速度。

LeetCode 練習