問題與適用場景
設計一個多租戶拉取式內容傳遞網路,共有 50 個接入點(PoP)。尖峰流量為每秒 200 萬個 GET 和 HEAD 請求,平均可快取 GET 回應大小為 256 KiB。客戶來源站合計最多安全承受每秒 2 萬次 回源。CDN 需要傳遞帶內容雜湊的靜態資源、圖片、影片分片,以及穩定 URL 下的可變文件。
目標是:正常條件下快取回應的 p99 首位元組時間低於 50 毫秒,請求可用性達到 99.99%,並在 60 秒內讓 99% 的健康 PoP 套用每次清除。系統還必須持續追蹤完整傳播和落後 PoP。這些數字是 面試假設,不代表任何供應商的實際承諾。
當使用者分布在不同地區、來源站距離主導延遲、物件可以重複使用,而且來源站頻寬或運算能力有限 時,這套設計適用。單一區域、重用很少的內部服務可能只需要區域反向 Proxy。面試範圍要求設計 CDN 核心機制;真實生產仍可在比較需求、安全邊界、維運成本和供應商故障模型後選擇託管 CDN。
面試官在考察什麼
第一,候選人能否把控制面與資料面拆開。租戶接入、來源站設定、憑證、快取規則和清除命令 需要持久的管理流程;管理路徑不可用時,請求服務仍要根據最後一個已知良好設定繼續執行。
第二,快取邊界是否正確。快取鍵漏掉一個回應差異維度,可能在語言、編碼、裝置或租戶之間回傳 錯誤內容甚至洩漏資料;把每個 Cookie 和請求標頭都加入鍵,則會把快取切碎到幾乎每次都未命中。 答案必須定義哪些請求可快取、哪些維度會改變回應,以及哪些私有回應必須繞過共享儲存。
第三,能否保護來源站。50 個 PoP 面對一個突然變熱的新物件時,即使還沒發生重試,也可能同時 產生 50 次冷填充。每層請求合併、區域屏蔽層、有限的來源站並行與重試預算分別解決不同問題。 任何故障都不能悄悄把每秒 200 萬次邊緣請求轉成回源流量。
第四,失效是否正確。只刪除當前位元組的清除會與舊填充競態,讓過期內容重新出現。高品質答案會 使用有序 generation 或墓碑,保證事件可冪等重播,並同時測量常見傳播目標和長尾。
最後,候選人要量化吞吐,明確一致性與過期內容契約,涵蓋故障和安全邊界,並用能證明使用者可見 行為的測試收尾,而非羅列供應商產品名。
回答前要釐清的問題
- CDN 是否擁有原始物件? 不擁有。本題是拉取式 CDN,客戶來源站仍是權威來源。
- 哪些方法可以快取? 基線只處理
GET和HEAD;不安全方法直接透傳,絕不進入共享快取。 - 哪些內容屬於私有內容? 帶授權資訊或使用者專屬 Cookie 的請求預設繞過共享快取,除非租戶提供經過評審、明確的分區政策。
- 可變內容允許多舊? 每條路由定義 TTL 和可選的有限陳舊視窗;緊急變化使用清除,但清除不能代替授權或撤銷檢查。
- 每個查詢參數都有效嗎? 只有真正改變回應的參數需要保留;租戶還可在正規化後移除已知追蹤參數。
- 是否需要位元組範圍? 大型媒體需要。快取鍵和中繼資料必須區分完整物件與已驗證分片,來源站還要提供穩定驗證器或版本化 URL。
- 如何選擇較近的 PoP? DNS 和/或 Anycast 把使用者路由到可達 PoP;BGP 選路不保證地理最近,所以仍要依賴健康度和實測延遲。
- 控制面故障怎麼辦? 現有流量使用帶簽名的最後一個已知良好設定;無法確認安全性的變更關閉並排隊等待恢復。
- 來源站故障時能否回傳舊內容? 只有路由明確允許
stale-if-error,而且最大年齡有界時才可以。 - 怎樣定義清除完成? 60 秒目標涵蓋 99% 的健康 PoP;系統還要分別記錄每個確認、落後節點、重試與探測結果。
30 秒回答框架
「我會拆分控制面和資料面。DNS 與 Anycast 將請求導向健康 PoP;邊緣選擇租戶簽名設定,正規化 白名單快取鍵,再查 RAM 與 SSD。未命中先在邊緣和區域屏蔽層合併,只有受預算約束的填充能存取 來源站;舊內容只在明確視窗內回傳。不可變資源使用版本化 URL;可變 URL 使用有序清除 generation 和墓碑,填充發布前比較 generation,防止舊位元組復活。最後驗證請求與位元組命中率、回源量、 命中 p99 首位元組時間、陳舊年齡、清除延遲和故障行為。」
分步深入設計
第一步:量化流量與來源站預算
用平均 GET 大小作為尖峰請求組合的規劃上界時,不計協定開銷的回應頻寬為:
2,000,000 請求/秒 × 256 KiB × 8 = 4.19 Tb/s如果尖峰持續一整天,邊緣回應位元組約為 45.3 PB。簡單平均到 50 個 PoP 後,每個 PoP 是每秒 4 萬個請求和 10.5 GB/s;實際流量存在地域和時間傾斜,因此容量要依據各 PoP 實測尖峰、餘量、 物件大小分位數和故障轉移增量,不能只看平均值。
來源站請求上限等於邊緣尖峰的 1%:
20,000 / 2,000,000 = 1%這不表示邊緣命中率目標簡單設成 99% 就足夠。屏蔽層命中、不可快取路由、填充、重新驗證和重試 都占用同一個來源站預算。來源站排程器需要租戶級、來源站級和全域並行與請求速率上限。
第二步:拆分控制面與資料面
控制面儲存租戶、網域、來源站身分、憑證、快取政策、正規化規則、陳舊上限、簽名 URL 金鑰和設定 版本。通過驗證的變更先持久提交,再編譯成帶簽名快照,透過版本化串流分發;PoP 確認已套用版本。 憑證私鑰進入專門的金鑰管理邊界,不能放在一般設定儲存中。
資料面負責 TLS、租戶查找、政策執行、請求正規化、快取、回源和日誌。快取命中時絕不同步存取 控制面資料庫。控制面故障期間,PoP 保留最後一個已知良好的簽名快照。舊設定有明確壽命;憑證 過期、租戶已撤銷或安全政策含糊時應關閉,不能永遠執行舊規則。
第三步:完成流量路由與租戶隔離
DNS 可以回傳區域網域或 Anycast 位址;Anycast 網路可由多個 PoP 宣告同一位址。網路先選擇可達 路徑,服務健康檢查再移除壞 PoP,並把流量排到其他位置。設計要觀察路由變化、轉移負載和延遲, 因為「最近」需要實測,BGP 不提供地理距離保證。
邊緣在任何快取查找之前,根據 SNI 和正規化 Host 對應租戶。租戶 ID 是每個快取命名空間隱含的 第一段。來源站只接受經過 mTLS、簽名請求、私有網路連線或輪替金鑰認證的 CDN 流量,不應保留可 公開繞過的入口。來源站位址和重新導向目標需要白名單,避免 SSRF。
第四步:定義快取資格與快取鍵
基線只在路由政策和 HTTP 欄位允許共享重用時快取成功的 GET 與 HEAD 回應。private、 no-store、授權資訊、使用者專屬 Cookie、Set-Cookie 與不支援的 Vary 通常都繞過儲存。 負面回應只能依狀態使用很短 TTL,避免一次瞬時故障變成長時間故障。
概念上的快取鍵為:
tenant_id | canonical_scheme_host | normalized_path | selected_query |
encoding_variant | approved_vary_dimensions | object_generation政策判斷、查找、日誌、填充和清除都使用同一次正規化結果。只有真正改變位元組的查詢參數和請求 標頭進入鍵。來源站依語言回傳不同內容時加入 Accept-Language 是正確做法;加入任意 Cookie 或 User-Agent 會讓鍵基數爆炸。回應的 Vary 必須符合該路由允許的維度,否則繞過快取。
新鮮度遵循租戶政策和 HTTP 語意:新鮮項目直接回傳;陳舊項目用 ETag 或 Last-Modified 重新驗證;stale-while-revalidate 與 stale-if-error 只能在明確邊界內使用。no-cache 表示 重用前必須驗證,no-store 表示不得儲存。CDN 政策不能放寬來源站更嚴格的隱私指令。
第五步:建立 RAM、SSD、屏蔽層與回源路徑
每個 PoP 在 RAM 中儲存熱門中繼資料和小物件,並使用帶准入控制的更大 SSD 快取。准入與淘汰綜合 請求頻率、位元組大小、最近存取和取得成本,避免一次大型冷物件掃描淘汰有價值工作集。來源站仍是 權威來源;邊緣快取遺失只影響效能,原始資料仍由來源站保存。
未命中時,singleflight 表依準確快取鍵合併呼叫者。一個呼叫者請求區域屏蔽層,其他呼叫者有限 等待或使用政策允許的舊項目。屏蔽層跨多個 PoP 再次查找和合併,只有選出的填充進入來源站排程器。 第二道合併邊界可以防止一個冷物件從每個 PoP 各觸發一次回源。
每次填充都有截止時間、最大大小、內容類型驗證、校驗和、租戶位元組預算與重試預算。指數退避和 抖動重試仍然消耗來源站預算。對沖只能用於冪等讀取,並用硬上限防止雙倍回源。大型物件可一邊向 使用者串流傳輸,一邊寫暫存快取;只有長度、驗證器和校驗和完整後才讓項目可見。
第六步:消除清除與填充競態
不可變資源預設使用內容定址檔名:新位元組產生新 URL,舊 URL 自然過期。穩定 URL 需要清除 API, 支援精確物件、經批准的前綴或標籤,以及租戶範圍的緊急清除。廣泛清除會造成全球未命中風暴, 所以必須限流並要求更強授權。
清除協調器先把 {tenant, selector, generation, issued_at} 提交到持久有序日誌,再回傳已接受。 PoP 冪等套用事件,推進選擇器最低 generation,刪除相符位元組,並保留足以涵蓋舊填充和延遲事件 的墓碑,隨後回報已套用 generation。分層扇出、重試和區域中繼避免一個慢 PoP 阻塞常見路徑。
快取發布填充物件前,要比較取得開始時捕捉的 generation 與目前最低 generation。若取得期間清除 已經推進,則丟棄這些位元組,或使用新 generation 重新取得。這一比較避免舊回應在刪除後到達並 復活過期內容;邊緣層和屏蔽層都要執行相同規則。
API 分別回報已接受、99% 已傳播,以及完成或逾時狀態。綜合探針從多個區域請求已清除鍵,並驗證 版本標頭或內容雜湊。安全撤銷仍應使用權威線上檢查或獨立受限的憑證壽命;60 秒清除 SLO 不等於 即時撤銷。
第七步:明確處理過載與故障
- PoP 故障:撤回或停止宣告路由,把流量排到健康 PoP,並為轉移流量預留容量。
- SSD 遺失:透過准入控制逐步重建;不要預熱所有物件,也不要繞過屏蔽層。
- 屏蔽層故障:選擇備用屏蔽層;若允許直連來源站,仍沿用相同回源預算。
- 來源站逾時或 5xx:只有政策允許時才回傳有界舊內容;否則明確報錯並避免重試放大。
- 控制面故障:使用最後一個已知良好簽名設定繼續服務;安全變更無法驗證時排隊並拒絕執行。
- 清除串流延遲:冪等重試並暴露落後 PoP;已知關鍵 generation 但位元組不可信時繞過或重新驗證。
- 熱門物件突發:合併填充、跨快取程序複製物件、防止單一程序 NIC 或鎖飽和,並限制濫用租戶。
第八步:保護並驗證完整系統
使用租戶範圍憑證終止 TLS,並保護來源站身分。限制請求大小、標頭數量、Range 數量和回應大小。 對含糊路徑與 HTTP 欄位只做一次正規化,防止請求走私與快取鍵解釋不一致。配額、金鑰、日誌、 清除權限和快取命名空間都依租戶隔離。簽名 URL 或 Cookie 在查找前驗證,其政策不能意外把私有 回應變成公開物件。
分別觀察請求命中率和位元組命中率、命中首位元組時間、未命中延遲、屏蔽層命中率、來源站 QPS 與頻寬、被合併呼叫者、快取鍵基數、淘汰位元組、陳舊年齡、各 PoP 清除延遲、設定版本、錯誤率 與故障轉移負載。日誌記錄隱私安全的鍵摘要、租戶、PoP、結果類型、年齡、generation、上游層 和 trace ID。
驗證涵蓋:50 個 PoP 同時冷啟熱門物件、清除與故意放慢的填充競態、重複和亂序清除事件、惡意 Vary、授權與 Cookie 繞過、部分 Range 填充、SSD 重新啟動、屏蔽層遺失、來源站限流、PoP 撤回 以及控制面故障。驗收要求包括正常條件下快取命中 p99 首位元組時間低於 50 毫秒、請求可用性 99.99%、回源不超過每秒 2 萬次,以及 99% 健康 PoP 在 60 秒內套用清除且舊內容不復活。
高品質參考回答
「我先固定容量邊界。每秒 200 萬個 256 KiB 回應約等於 4.19 Tb/s 尖峰回應流量;來源站只能承受 邊緣請求量的 1%,所以來源站保護是一條硬不變量。
我把持久控制面與請求資料面分開。租戶設定、憑證、來源站身分、快取規則和清除都版本化並稽核。 PoP 使用帶簽名的最後一個已知良好設定服務,不會每次請求都查控制面資料庫。DNS 與 Anycast 把 使用者導向健康 PoP;SNI 與 Host 在帶租戶命名空間的快取查找前確定租戶。
快取鍵包含租戶、正規 URL、只包含會改變回應的查詢與標頭維度,以及 generation。私有、授權、 no-store 與不安全回應繞過共享快取。命中來自 RAM 或 SSD;未命中先在邊緣合併,再到區域屏蔽層 再次合併,最後通過來源站級和全域預算。新鮮、重新驗證和有限陳舊路徑互相分開。
不可變資源使用版本化 URL。可變 URL 清除先向持久日誌提交遞增 generation。每層保留最低允許 generation 和墓碑;填充發布前檢查 generation,因此清除前發出的請求不能在清除後恢復舊內容。 我會明確暴露 99% 已傳播與完整狀態,不隱藏落後節點。
最後用請求和位元組命中率、回源 QPS、命中 p99 首位元組時間、陳舊年齡、清除延遲和設定版本 證明設計,再注入熱門冷未命中、清除填充競態、PoP 與屏蔽層故障、來源站限流、亂序清除、快取鍵 污染和控制面故障,並把四項既定 SLO 作為驗收標準。」
常見錯誤
- 把地理最近 PoP 說成保證 → BGP 選擇網路路徑,不按直線距離 → 測量延遲與健康度,並設計路由撤回和故障轉移。
- 把所有請求標頭加入鍵 → 基數爆炸,絕大多數流量未命中 → 只允許改變回應的維度,並拒絕不支援的
Vary。 - 快取命名空間漏掉租戶 → 相同 URL 可能跨租戶重用 → 查找前推導租戶,並把它作為隱含鍵前綴。
- 清除只刪除位元組,沒有 generation → 舊的在途填充可能重新發布 → 推進墓碑,插入前比較 generation。
- 只加邊緣鎖 → 50 個 PoP 仍可發出 50 次回源 → 屏蔽層再次合併,並保留全域回源預算。
- 每次回源失敗都重試 → 重試會放大故障 → 使用截止時間、有限預算、抖動,以及路由專屬舊內容或錯誤政策。
- 用清除實現即時安全撤銷 → 傳播存在可測長尾 → 安全決策使用權威檢查或有限憑證壽命。
- 只回報請求命中率 → 大量小命中會隱藏昂貴的大型物件未命中 → 同時追蹤位元組命中率、回源頻寬、大小分布與取得成本。
追問深入討論
追問一:怎樣支援大型影片物件和 Range 請求?
優先使用不可變分片 URL,並在可行時快取完整分片。合併範圍前驗證 Content-Range、物件長度、 驗證器和 generation。限制 Range 數量及放大倍數,絕不能讓兩個回應表示共用同一個部分物件鍵。 超大型物件可依受控網格對齊分塊,使重疊請求重用位元組,同時避免任意碎片。
追問二:怎樣防止快取鍵投毒?
對 URL 和 HTTP 欄位只正規化一次,拒絕含糊編碼,並把所有會改變來源站位元組的已批准輸入加入鍵。 不要把來源站用於選擇回應、卻未進入鍵的標頭繼續透傳。測試衝突標頭、重複欄位、路徑編碼、 查詢順序和 Host 正規化,確保邊緣與來源站解釋一致。
追問三:個人化 HTML 可以進入邊緣快取嗎?
只有在產品和安全契約明確時才可以。更安全的方案是快取公開殼,再單獨讀取私有資料。如果必須 快取完整 HTML,要用有界、已驗證的身分或群組分區,禁止共享重用,定義登出行為,並測試跨使用者 隔離。把任意工作階段 Cookie 放進公開快取鍵既危險,也會摧毀命中率。
追問四:怎樣在單一全球屏蔽層和區域屏蔽層之間選擇?
單一屏蔽層最能合併未命中,卻可能增加距離並集中故障;區域屏蔽層降低延遲和爆炸半徑,但可能讓 多個區域分別回源。根據來源站位置、可快取性、區域需求、可接受延遲和回源預算選擇,再實際測試 屏蔽層故障轉移,不能假設一種拓撲適合所有場景。
追問五:怎樣發布新的快取鍵政策?
把它編譯成新設定版本,同時影子計算新舊快取鍵;在不使用新鍵回傳內容時,比較基數、命中率、 隱私分類和回源量。按租戶與 PoP 灰度,保留可回復版本,只預熱已證實的熱門物件。快取鍵變更會 製造冷快取事件,因此也必須進入一般填充的來源站預算。
追問六:99.99% 可用性一定需要多 CDN 嗎?
不一定。若一個供應商的實測故障模型、PoP 冗餘、路由撤回、來源站設計與維運能支援目標,單 CDN 也可能足夠。多 CDN 能降低部分供應商風險,卻會增加 DNS 或調度一致性、重複設定、清除協調、 日誌正規化、憑證管理與共享屏蔽層問題。只有測試證明新增控制面能改善既定可用性目標時才引入。