分散式快取 (Distributed Cache)
Distributed Cache 的難點不在於快,而在於它自己變成一個需要被設計與維運的分散式系統。
當 cache 從單機升級成 cluster,問題重心從「記憶體夠不夠」轉移到「key 怎麼分配、節點掛了怎麼辦、rebalancing 期間會不會打掛資料庫」。它不再只是加速層,而是一個獨立的 distributed system,必須處理 sharding、replication、failover、一致性、流量偏斜。
從單機到 Cluster:問題的重心轉移
單機 cache 的問題集中在記憶體管理與讀寫流程;一旦變成多節點 cluster,重心整個換掉。
| 維度 | 單機 Cache | Distributed Cache |
|---|---|---|
| 核心問題 | eviction(LRU)、TTL、cache-aside vs write-through、stampede | key 分配、節點故障、網路延遲、流量偏斜 |
| 容量上限 | 單機記憶體 | 水平擴展(加節點) |
| 故障影響 | 整個 cache 沒了 | 部分 shard 失效,可 failover |
| 本質 | 記憶體優化 | 獨立的分散式系統 |
典型觸發點:電商原本一台 Redis 存所有商品頁,流量成長到單機記憶體撐不住 → 升級 Redis Cluster。從這刻起,要回答的是「key 怎麼分配」「某台掛掉影響多大」「rebalancing 期間流量會不會暴增」。
Sharding 與 Consistent Hashing
升級成 cluster 後第一個必須解決的問題是 key 的分布。
簡單做法 hash(key) % N:
N=5 → key "user:123" 落在 node 2
擴容成 N=6 → 同一個 key 落在 node 4 ← 幾乎所有 key 重新映射!
Consistent Hashing(hash ring):
node A
/ \
node D node B 加入 node E 只影響 D→E 之間的 key
\ / 其他 key 位置不變 → 只有 1/N 失效
node C
| 做法 | 擴容/縮容時的衝擊 | 適用 |
|---|---|---|
hash(key) % N |
幾乎所有 key 重新映射 → 大量 miss | 節點數固定、不會變動 |
| Consistent Hashing | 只影響一小部分 key(約 1/N) | cache cluster 預設選擇 |
% N 在高流量系統是災難
社交平台把 5 台節點擴容成 6 台,若用 % N,幾乎所有 profile cache 同時失效。接下來幾分鐘資料庫承受平常數倍流量 —— 等同瞬間 cold start。Consistent Hashing 把這個衝擊降到只影響部分 key。
但要記住:Consistent Hashing 只解決平均分布,不解決流量偏斜(skew)。
詳見 02-Distributed-Systems/04-Consistent-Hashing,那裡有 virtual node 如何讓分布更均勻的完整推導。
Hot Key 問題
實務上最常見的不是平均負載,而是極端負載。熱門直播間、明星帳號、首頁推薦資料,單一 key 可能佔整體流量 20% 以上。
即使 key 均勻分布在節點上,只要這個 hot key 對應到某一台機器,那台機器就成為 bottleneck。分布均勻 ≠ 流量均勻。
┌─ node A (10% 流量)
全部 key ─────┼─ node B (10% 流量)
均勻分布 ├─ node C ★ 30% 流量 ← hot key "livestream:99" 全壓這台
└─ node D (10% 流量)
常見解法:
| 解法 | 做法 | 取捨 |
|---|---|---|
| Key 多副本 | 同一 key 存多份(key#0~key#N),client 隨機讀 |
讀分散,寫要更新多份 |
| Local in-process cache | application server 本地再加一層快取 | 最快、省網路,但各機可能 stale |
| 拆分邏輯 key | user:123:profile 拆成 :0/:1/:2 分散到多節點 |
讀寫邏輯變複雜 |
這類問題通常在流量真的變大之後才浮現,設計階段難以完全預測。所以 distributed cache 要能 scale(擴展),也要能應對 skew(分布不均)。面試提到 hot key 緩解策略會明顯加分。
Cache Replication 與 High Availability
很多團隊早期假設「cache 掛掉沒關係,反正能從資料庫重建」。在高流量系統中,這個假設會造成事故。
正常:50k QPS ─┬─ 45k 命中 cache
└─ 5k 打 DB(DB 容量剛好夠)
cache cluster 全掛:
50k QPS ──────────────→ 全部回源 DB
DB 只有 5k capacity → 瞬間 10× 流量 → 雪崩
當 cache 成為資料庫前面的保護層,一旦整個 cluster 失效,所有請求直接回源。原本 90% 由 cache 處理 → 資料庫瞬間承受 10 倍流量。
因此 distributed cache 通常需要 replication:每個 shard 配一個 replica,primary 掛掉時 client 快速 failover。
因為 cache 本身不承擔最終資料來源的責任 —— source of truth 是資料庫。replica lag 或 failover 期間的短暫不一致,最壞情況只是讀到 stale 資料,TTL 到期後就會修正。為了一個「可重建的加速層」付出 synchronous replication 的延遲代價並不值得。但設計時仍要知道你的 cache 能容忍多少不一致(例如帳戶餘額 vs 商品瀏覽數,容忍度完全不同)。
對照 05-Database-Advanced/03-Replication:資料庫 replication 常需考慮 sync vs async、讀寫分離;cache replication 幾乎一律 async,因為丟資料的代價低得多。
Failure Modes(故障模式比資料庫更隱晦)
Distributed cache 的失效通常是性能退化,而不是明顯 crash —— 這讓它更難偵測。
Cache Stampede(驚群 / 雪崩)
大量 key 同時過期 → 同時 miss → 同時回源。distributed 環境會放大:多個 app server 同時 miss、多個 cache node 同時回源,資料庫壓力瞬間飆升。
TTL 全設 300s:
t=0 寫入 1000 個 key,TTL=300
t=300 全部同時過期 → 1000 個請求同時打 DB ← stampede
加 jitter(TTL=300 ± random(0~60)):
過期時間分散在 t=240~360 → DB 壓力平滑
| 緩解 | 機制 |
|---|---|
| TTL jitter | TTL 加隨機抖動,避免 key 同時過期 |
| Single-flight | application 層確保同一時間只有一個請求去回源,其餘等結果 |
Cold Start
新節點加入 cluster 或整個 cluster 重啟時 cache 為空 → 大量 miss。夜間部署、自動擴容時很常見。緩解:新節點加入後先預熱,或限流逐步切換,避免瞬間把所有請求導到空節點。
Partial Node Failure(最棘手)
比起直接 crash,更常見的是某台 node 變慢但沒完全失效:
node C 變慢 → client timeout → retry
→ retry 流量打到其他 node 或 DB → 連鎖效應 → 整體崩潰
因為 client 還在對它發請求、timeout、retry,重試流量會擴散到健康節點和資料庫。client 端必須有 合理 timeout + 重試上限 + circuit breaker,避免單點性能退化演變成整體崩潰。這正是 01-Networking/07-Fault-Handling 的核心模式。
一致性問題
Cache 天生就是放寬一致性的設計。distributed 環境讓一致性管理更複雜。
Cache Invalidation
寫 DB 後廣播 invalidation 到所有 node:
node A ✓ 收到 → 刪舊資料
node B ✓ 收到 → 刪舊資料
node C ✗ 漏收 → 持續回傳舊資料(永久 stale!)
單機 invalidation 已經很難,多節點下若有節點漏收訊息就會永久保留舊資料。
即使 invalidation 失敗,資料最多 stale 一段「可接受時間」(TTL),而不是永久錯誤。用 TTL 當兜底,把一致性問題從「正確性」降級成「新鮮度」。
Write Path 與 Cache 同步
| 模式 | 寫入流程 | 多節點下的風險 |
|---|---|---|
| Cache-aside | 寫 DB → 刪 cache | 刪除失敗 → 某些節點殘留舊資料 |
| Write-through | 同時寫 cache + DB | 兩者一致性難保證(部分成功) |
實務上團隊通常選簡單且可推理的模式(cache-aside + TTL),而不是追求強一致。寫入模式的細節見 07-Caching-Storage/01-Caching。
Multi-Region:每區一套,不跨區複製
US Region EU Region
┌──────────┐ ┌──────────┐
│ cache US │ │ cache EU │ ← 不跨 region 複製 cache
└────┬─────┘ └────┬─────┘
└──── DB(跨區複製,source of truth)────┘
跨 region 時通常每個 region 有自己的 cache cluster,不跨區複製。理由很實際:cache 是可重建的資料,不值得承擔跨區 replication 的複雜度與延遲。EU 故障時流量切到 US 會出現短暫 cold cache,但資料仍在 DB 裡。強調區域隔離,而非全球一致的 cache。
面試話術
完整回答要展示你理解 cache 是 performance optimization,但 distributed cache 本身需要完整的分散式設計思維。涵蓋六個面向:① 為何要分散 ② key 如何分片 ③ 擴縮容如何影響命中率 ④ cluster 故障如何保護 DB ⑤ stampede / hot key 處理 ⑥ 是否需要 replication 與一致性要求。
「我們用 sharded distributed cache,透過 consistent hashing 分配 key,避免擴縮容時大量失效。每個 shard 配一個 replica,primary 掛掉時 client 快速 failover。為了防止 stampede,TTL 加入隨機 jitter,並在應用層做 single-flight,確保同一時間只有一個請求回源。對於 hot key,在應用層加本地快取或做 key-level replication。如果整個 cache cluster 出問題,系統會限制回源流量並啟動降級模式,保護資料庫。」
常見 Deep Dive
hash(key) % N?因為節點數 N 一變,幾乎所有 key 的映射都會改變,造成大量 cache miss、瞬間 cold start,資料庫被打到平常數倍流量。Consistent hashing 讓節點變動只影響約 1/N 的 key。代價是實作較複雜,且需要 virtual node 才能讓分布夠均勻。
因為 consistent hashing 只保證 key 均勻分布到節點,不保證 流量均勻。一個明星帳號的 key 雖然只佔一個 slot,但可能吸走 20%+ 的請求,全壓在它所在的那台機器。解法是讓這個 key 不再是「單點」:多副本隨機讀、application 本地快取、或拆成多個邏輯 key。
這是最危險的故障:所有請求回源,DB 瞬間承受數倍流量而雪崩。三層防護:① replication + failover 讓單 shard 故障不至於全掛;② client 端 timeout + retry 上限 + circuit breaker 避免重試風暴;③ cluster 級故障時限流回源 + 降級模式(回傳預設值/舊資料/部分功能),主動犧牲體驗保護 DB。詳見 12-Ops-and-Reliability/02-Overload-Protection。
兩者都防 stampede 但角度不同。Jitter 從「源頭」下手:打散 key 的過期時間,避免它們同時失效。Single-flight 從「回源」下手:當一個 key miss 時,只放一個請求去查 DB,同 key 的其他並發請求等它的結果共享,而不是各自回源。前者降低同時過期機率,後者保證即使同時 miss 也只打 DB 一次。生產系統通常兩個都用。
因為 cache 是「可重建」的資料,source of truth 在資料庫。跨區複製 cache 要付出高延遲、高頻寬、複雜的一致性協調,換來的只是「某 region 掛掉時少一次 cold start」。代價遠大於收益。正確做法是每區自治 cache + DB 跨區複製;region failover 時容忍短暫 cold cache。
廣播式 invalidation 無法保證每個節點都收到(網路分區、節點重啟)。所以不能把正確性寄託在 invalidation 上。主流做法是 TTL 兜底:即使 invalidation 漏掉,資料最多 stale 到 TTL 過期。把問題從「可能永久錯誤」降級成「短期不新鮮」,這是工程上可接受的取捨。
自我測驗重點
| 問題 | 重點 |
|---|---|
| 單機 vs distributed 重心差異 | 從記憶體管理 → 變成獨立的分散式系統 |
% N 為何不行 |
節點變動幾乎全部 key 重映射 → 大量 miss / cold start |
| Consistent Hashing 解什麼 | 只解平均分布,不解 流量偏斜(hot key) |
| Hot key 三招 | 多副本隨機讀 / 本地快取 / 拆邏輯 key |
| cluster 全掛的後果 | 所有請求回源 → DB 瞬間 N 倍流量 → 雪崩 |
| Cache replication 一致性 | eventual consistency 即可,cache 非 source of truth |
| Stampede 兩招 | TTL jitter(源頭打散)+ single-flight(回源去重) |
| Partial node failure 為何危險 | 變慢非 crash → timeout/retry 風暴 → 需 circuit breaker |
| Invalidation 策略 | TTL 為主、invalidation 為輔(漏訊息只造成短期 stale) |
| Multi-region cache | 每區一套、不跨區複製;failover 容忍 cold cache |
Related Notes
- 07-Caching-Storage/01-Caching — 快取基礎:cache-aside / write-through、TTL、eviction、stampede 的單機版本
- 07-Caching-Storage/04-Redis — distributed cache 最常見的實作載體;Redis Cluster 的 sharding 與 replication 細節
- 02-Distributed-Systems/04-Consistent-Hashing — key 分片的核心演算法;virtual node 如何讓分布更均勻
- 10-Design-Patterns/01-Scaling-Reads — distributed cache 是擴展讀取的主力手段,吸收 90%+ 的讀流量
- 05-Database-Advanced/03-Replication — cache replication 與 DB replication 的對照:cache 可用 eventual consistency
- 05-Database-Advanced/02-Sharding — cache sharding 與資料庫 sharding 共用「key 分片」思維
- 01-Networking/07-Fault-Handling — partial node failure 需要 timeout / retry 上限 / circuit breaker
- 12-Ops-and-Reliability/02-Overload-Protection — cluster 全掛時的限流回源與降級模式,保護資料庫
- 06-Database-Tech/01-Database — cache 保護的對象;理解 DB 容量上限才知道回源風暴的代價