CDN
CDN 是 Content Delivery Network(內容傳遞網路):把內容複製到離使用者近的邊緣節點,大部分請求不用跨海回到原始伺服器,延遲和原站負載一起下降。
程式碼路徑
1 min
1.0
200 (20%)
經過 CDN(平均)直接連原站
每 100 個請求
65 在邊緣答掉
35 回原站
命中率
65.5%
平均延遲(直連)
54 ms (109 ms)
原站每秒請求
13.8 / 40
舊版本回應
30.5%
TTL 設為 1 min 時,邊緣節點自己答掉 65% 的請求,原站每秒只收到 13.8 個請求,而不是 40 個。亞洲受惠最多:平均 85 ms,直連原站要 200 ms,因為每次命中都省掉離原站最遠的那一趟。TTL 長的代價:30.5% 的回應是原站已經換掉的舊版本。
假設的往返時間:使用者到自己地區的邊緣節點 20 ms;邊緣節點回原站,北美/歐洲/亞洲分別 30/95/185 ms;使用者直連原站 40/110/200 ms。1,000 個物件、熱度依 Zipf 分佈,每秒 40 個請求、原站每秒更新 1 個物件,共 10 分鐘;前 60 秒暖機不計。
亮起來的是這一步執行的程式碼
interface Origin { fetch(key: string): string; // far away: 30-185 ms} class EdgeCache { // A Map keeps insertion order: the first key is the least recently used. private cache = new Map<string, { body: string; fetchedAt: number }>(); constructor(private capacity: number, private ttlSec: number, private origin: Origin) {} get(key: string, now: number): { body: string; hit: boolean } { const entry = this.cache.get(key); if (entry && now - entry.fetchedAt < this.ttlSec) { this.cache.delete(key); this.cache.set(key, entry); return { body: entry.body, hit: true }; } if (entry) this.cache.delete(key); const body = this.origin.fetch(key); if (this.cache.size >= this.capacity) { this.cache.delete(this.cache.keys().next().value!); } this.cache.set(key, { body, fetchedAt: now }); return { body, hit: false }; } // Called on every edge when the origin changes an object. purge(key: string): void { this.cache.delete(key); }}模型假設與範圍
- 這是可重現的教學模型;延遲、容量、故障率與工作負載是設定或樣本,不能直接當作正式系統的效能承諾。
- 三個區域的邊緣快取與單一來源,TTL/purge 控制版本新鮮度;往返延遲是假設,沒有真實路由或故障量測。
什麼時候用
- 使用者遍布各地,而內容大家讀的都一樣:圖片、影片、JavaScript、CSS(Cascading Style Sheets)、下載檔、公開的 API 回應。
- 原站撐不住尖峰流量:大部分請求在邊緣就被答掉,原站只看到沒命中的那一部分。
- 內容可以容忍短時間的舊版本;不能容忍的(價格、庫存、個人資料)就設短 TTL、更新時清除,或乾脆不經過快取。
和其他主題的關係
出現在這些架構裡
時間與空間複雜度(Big O)
| 操作 | 平均 | 最差 |
|---|---|---|
| 邊緣節點回應一個請求 沒命中時再加一趟回原站:時間花在網路,不在計算 | O(1) | O(1) |
| 清除一個物件 E 是邊緣節點數;每一台都要通知到 | O(E) | O(E) |
空間:O(E·C),每個邊緣節點各放 C 個物件,熱門的物件在每一台都有一份
和其他做法比
| 命中率 | 原站每秒請求 | 平均延遲 | 舊版本回應 | |
|---|---|---|---|---|
| TTL 0(不快取) | 0% | 40.0 | 116 ms | 0.0% |
| TTL 10 秒 | 46% | 21.5 | 72 ms | 9.9% |
| TTL 1 分鐘 | 65% | 13.8 | 54 ms | 30.5% |
| TTL 5 分鐘 | 69% | 12.3 | 50 ms | 45.8% |
| TTL 5 分鐘+更新時清除 | 66% | 13.6 | 53 ms | 0.0% |
同一串請求(熱度 s = 1、每個邊緣放 200 個物件)只換 TTL 和清除策略。TTL 越長,命中率越高、原站越輕鬆,送出舊版本的比例也越高;更新時主動清除可以兩者兼得一部分,但需要原站知道每一個邊緣節點在哪裡。
真實世界裡的它
- Cloudflare、Akamai、Fastly、Amazon CloudFront:全球幾百個節點,用 DNS 或 Anycast 把使用者導到最近的一個。
- Netflix 的 Open Connect 直接把機器放進網路業者的機房,熱門影片在半夜先推過去。
- HTTP 的
Cache-Control: max-age和s-maxage就是在設定這裡的 TTL。
取捨與陷阱
- TTL 是新鮮度和負載的交換:設長了命中率高,但使用者會看到舊內容;檔名加上版本(
app.3f9a.js)就能讓靜態檔永遠不用清除。 - 沒命中比直接連原站還慢:多了使用者到邊緣這一段。冷門內容、個人化內容放上 CDN 反而變慢。
- 快取的鍵沒有包含會改變內容的東西(語言、登入狀態、裝置),就會把 A 的頁面送給 B。