01

總覽與定位

CDN 是一群地理分散的共用 HTTP 快取。本篇用「一個請求在邊緣節點上發生的事」貫穿:先算 Cache Key,再決定 HIT/MISS/回源,最後才談過期、失效與誰去打 origin。

應用層快取(Redis、本地 LRU)服務的是你自己的服務:key 是你定的,失效是你 DEL 的。CDN 服務的是HTTP 表徵:它站在瀏覽器與 origin 之間,讀 Cache-Control、算一把從 URL 與選定標頭組出來的 key,把同一份回應賣給同一個 PoP 上的下一個陌生人。兩者都能降延遲、都能打穿 origin,但契約完全不同。

一個 GET 落到邊緣之後,生命週期幾乎總是這四步。後面每一章對應其中一步,不跳著講產品功能清單。

1 · 算出 Cache Key
Normalize
哪些 query、Header、Cookie 進 key。進多了碎片化,進少了串資料。Vary 是這一步的帳單。
2 · 查這把 key
HIT / MISS / Collapsed
命中就回;未命中才回源。同一 PoP 上的併發 miss,邊緣可以合併成一趟 origin GET。
3 · 判斷能不能賣
Fresh / Stale / Revalidate
TTL 內直接賣。過期後可 stale-while-revalidate 先賣再更新,或 stale-if-error 在 origin 掛掉時繼續賣。
4 · 誰去打 origin
PoP → Shield → Origin
未命中不一定直打應用。Origin Shield 與分層快取把全球 miss 收斂成靠近 origin 的少數幾次。
flowchart LR
    C["Client GET"] --> P["就近 PoP"]
    P --> K["算 Cache Key"]
    K --> L["查快取"]
    L -->|HIT| C
    L -->|MISS| O["回源 / Shield"]
    O --> C
  

看「查快取」分出的兩條邊:HIT 那條直接折返 Client,origin 完全不知道這次請求;只有 MISS 那條才往右走到回源或 Shield,而且回來之後還要寫進邊緣的 store。

座標協定、DNS、應用快取都不在這篇重講

HTTP/1.1 與 HTTP/2 已經講過訊息語義、狀態碼、連線多路復用,以及 freshness/validation 這對詞的定義——本篇只談共用快取怎麼執行這些指令。
使用者如何被導到某一個 PoP,只講它如何把快取切成地理分片;DNS 解析、CNAME、TTL 交給 DNS 深入解析。
Cache-Aside、Write-Through、LRU/LFU 是 快取策略 的題。Origin 被隨機空 key、熱 key 到期、TTL 對齊打死,是 快取失效模式與雪崩防護 的題——本篇只談邊緣側對應的 collapsing、stale 售賣與 Shield。

一句話答法CDN 是地理分散的共用 HTTP 快取。請求在邊緣先正規化成 Cache Key,再走 HIT/MISS/回源;過期策略與誰去打 origin,都是這條生命週期上的旋鈕,不是另一套產品。
02

Cache Key 正規化與 Vary 的代價

命中率幾乎總是先被 key 設計殺死,而不是被 TTL 殺死。進 key 的每一個位元組,都在複製一份快取物件。

邊緣看到的不是「/hero.jpg 這張圖」,而是一把由 URL 與選定請求欄位組出來的字串。同一張圖,十種 query 排列就可能變成十份物件——每一份都 miss 一次、佔一份記憶體、過期一次。

cache_key = host + path + selected_query + vary_headers + optional_cookie_subset

正規化的工作是刪掉不改變表徵的維度,留下真正會讓回應不同的維度。排序 query、去掉空值、主機名小寫,是免費的;難的是決定哪些欄位「算內容」。

1 · Query 參數:內容 vs 追蹤

該進 key
會改變 bytes 的參數
  • v/hash:資產版本。進 key 才能讓 app.js?v=7 與 v=8 分成兩份,這正是版本化 URL 要的
  • w/h/format:圖片衍生尺寸。不同像素就是不同物件
  • locale/currency:同一路徑下的不同表徵
不該進 key
不改變 bytes 的噪聲
  • utm_*、gclid、fbclid:廣告歸因。進 key 等於幫每次行銷活動複製一份快取
  • 隨機 _/cache-buster:客戶端自己在殺命中率
  • 排序不同、值相同:?a=1&b=2 與 ?b=2&a=1 必須正規化成同一把

左右兩欄的判準只有一條:這個參數會不會改變回應的 bytes。會改變的留下(左欄),只改變觀測的丟掉(右欄)。

2 · Header 與 Cookie

共用快取預設不該把 Cookie 放進 key。Session cookie 幾乎每人一把,key 基數 ≈ 活躍用戶數,命中率趨近於 0,還有把 A 的私人頁面賣給 B 的風險——後者該用 Cache-Control: private/no-store 直接禁止共用,而不是「用 cookie 當 key 假裝隔離」。

若產品真的需要「登入/未登入兩份 HTML」,正確做法是把身分正規化成一個低基數維度(例如 logged_in=0|1),而不是整段 Cookie 原樣進 key。

3 · Vary 的帳單

Vary 告訴共用快取:「這份回應還依哪些請求標頭而變」。它不是註解,是 key 的乘法器。

Vary 物件倍數 實務
Accept-Encoding gzip/br/identity… 幾乎每個 CDN 都會自己談壓縮並正規化;不要再手動加出第三種編碼
Accept-Language 語言數 × 品質值變體 瀏覽器送的字串很髒(en-US,en;q=0.9)。不正規化就爆炸。能改用 ?locale= 就不要 Vary 語言
Cookie ≈ 用戶數 等價於這條 URL 在共用快取上不可快取
User-Agent 裝置/瀏覽器長尾 經典碎片化。要分桌面/手機,用正規化後的裝置類別,不要原串

Vary 每加一個高基數標頭,命中率就按那個基數相除。面試被問「為什麼上了 CDN 命中率只有 20%」,先把 Cache Key 攤開,十次有八次答案在這裡。

陷阱正規化不足與正規化過度是兩種相反的事故

不足:追蹤參數進 key,快取被廣告活動切成碎片,origin 流量看起來「怎麼清不掉」。過度:把 locale 從 key 拿掉,英文快取賣給中文用戶——這是正確性 bug,比 miss 更貴。規則是:會改變表徵的留下,只改變觀測的丟掉。

一句話答法Cache Key 只收會改變 bytes 的維度:內容型 query 進、utm/session cookie 不進。Vary 是乘法器;Vary Cookie 或 User-Agent 幾乎等於這條 URL 不要走共用快取。
03

命中、未命中與回源

key 算完之後,邊緣只有三種工作:把已有的表徵賣掉、去 origin 取一份,或掛在別人正在取的那一趟上。

使用者被導到哪個 PoP,只在這一步有意義:快取是地理分片的。東京 HIT 幫不到倫敦 MISS。Anycast 或 Geo-DNS 把請求送到「近」的節點,於是熱門物件在每個 PoP 各自暖機一份;冷門物件則在很多 PoP 上永遠是 miss。解析過程本身交給 DNS 篇,這裡只留下這個後果:全球命中率 ≠ 單一 PoP 命中率,purge 也必須扇出到所有分片。

同一條 URL、同一把 key TPE PoP LHR PoP
這個 PoP 本地有沒有這把 key 有(已被前一位使用者暖過) 沒有
這一次的判定 HIT,停在邊緣 MISS,仍要回源
origin 看到的流量 0 1 次 GET
一次 purge 要扇出到 這一片 這一片,兩片都收到才算作廢完成

兩欄是同一條 URL、同一把 key,結果卻相反——快取是地理分片的,所以全球命中率永遠低於單一 PoP 的命中率,purge 也必須扇出到每一片。

HIT / MISS / 回源

  • HIT(fresh):物件在、且仍在 TTL 內。邊緣直接回 200,origin 完全不知道這次請求。
  • MISS:這把 key 在這個 PoP 上沒有。邊緣成為 origin 的 HTTP 客戶端,取回後寫入自己的 store,再回給使用者。
  • Expired + revalidate:物件在,但過期了。邊緣帶 If-None-Match 問 origin;304 則刷新新鮮度、body 沿用,200 則換新表徵。

邊緣側的請求合併(Request Collapsing)

同一 PoP、同一把 key、同一瞬間的一群 miss,不必每人打一趟 origin。第一個成為 in-flight 的請求去回源,後來者掛在同一趟結果上。這是邊緣版的 Singleflight:範圍是這一台邊緣節點(或這一個 PoP),不是全球。

它擋什麼
單一 PoP 上的 herd 乘數
  • 熱資產剛過期、或剛部署的新 hash 檔名,一個 PoP 可能同時湧入幾千個 miss
  • 合併把這個 PoP 的 origin GET 從 N 收成 1
  • 使用者仍付那一趟回源的延遲(尾部被對齊)
它不擋什麼
跨 PoP · 不同 key · origin 自己的災害
  • 200 個 PoP 同時 miss,origin 仍可能看到 200 次回源——那是 Shield 的題
  • Cache Key 碎片化後,每把 key 各合併各的,總回源次數不會下降
  • origin 被隨機空 URL 打穿、被應用層熱 key 打死,是 快取失效模式 的題;邊緣 collapsing 救不了「每把 key 只出現一次」

左欄是 collapsing 真正收掉的東西——單一 PoP 內併發 miss 的乘數;右欄是它的射程邊界,跨 PoP 與 key 碎片化都不在裡面。

關鍵負快取在邊緣同樣存在,但契約是 HTTP

404/301 也可以被快取,指令仍是 Cache-Control(或 CDN 的預設 negative TTL)。這能擋住「已下線的舊資產被爬蟲反覆打 origin」,代價是上線後的短暫假陰性——跟應用層空值快取同一類窗口,只是旋鈕在 HTTP 標頭,不在 Redis SET nil。Bloom Filter、互斥重建鎖不要答進這段。

一句話答法HIT 停在 PoP,MISS 才回源;同一 PoP 的併發 miss 用 collapsing 收成 1 次。跨 PoP 的 herd 要靠 Shield,key 碎片化則合併也救不了。
04

TTL 與 stale-while-revalidate/stale-if-error

過期不是一扇關上門。邊緣可以在「新鮮」「過期但仍可賣」「origin 掛了仍可賣」三個窗口裡做不同的事。

共用快取看的新鮮度,主要來自回應上的 s-maxage(沒有才退到 max-age)與物件進入快取後累計的 Age。當 Age < TTL,物件是 fresh,請求連 origin 的門都不敲。TTL 到期之後,預設行為是先驗證再賣——使用者付一次 RTT。兩個擴充指令把這扇門打開一條縫。

fresh = Age < TTL;SWR 窗口 = TTL ≤ Age < TTL + swr;SIE 在 origin 失敗時允許 Age < TTL + sie

stale-while-revalidate:先賣舊的,背景更新

物件剛過期、但仍在 SWR 秒數內:邊緣立刻把舊表徵回給使用者(狀態仍是 200),同時對 origin 發一趟條件式 GET。更新成功就換新物件,失敗就繼續持有舊的直到窗口結束。

使用者看到的內容,與背景正在抓的內容,在這一秒內是故意分叉的。這就是 Demo 時間軸要對的帳:讀路徑的延遲被壓成 HIT,新鮮度落後最多一個 SWR 窗口。

適用
可容忍短暫舊頁的讀多寫少
  • 行銷頁、商品列表、靜態組態、公開 JSON
  • 把「TTL 到期瞬間的 herd」從使用者路徑拿掉:到期後第一個請求不再擋住所有人
  • 與邊緣 collapsing 疊加:背景更新本身也可以合併
代價
明確的 stale 預算
  • 產品必須說得出「最多舊 N 秒可以接受」。庫存、價格、權限決策通常不行
  • 窗口內 origin 一直失敗,使用者會一直看到同一份舊頁——這是功能,不是 bug,但要有告警
  • 它不是應用層的邏輯過期:契約在 HTTP 標頭,任何遵守 RFC 5861 的共用快取都該懂

右欄第一條就是採用 SWR 的前提:產品說不出「最多舊幾秒可以接受」,這個窗口就不該開。

stale-if-error:origin 掛了仍賣舊的

SWR 處理的是「過期但 origin 還活著」。SIE 處理的是「origin 5xx/連不上」。在 SIE 窗口內,邊緣可以在驗證失敗時繼續回舊表徵,而不是把 origin 的 502 轉發給全世界。

這是邊緣對 origin 故障的降級,不是應用層雪崩防護的替代品。雪崩篇用 TTL 抖動與 Singleflight 保護 origin 自己;這裡是 origin 已經倒了,邊緣決定還要不要把最後一份已知良好的 bytes 賣出去。

窗口 使用者拿到 origin 參與 典型標頭
Fresh 快取中的表徵 不參與 s-maxage=N
SWR 立刻拿到舊的 背景條件式 GET stale-while-revalidate=M
SIE(origin 失敗) 繼續拿舊的 失敗被吞掉 stale-if-error=K
窗口外 + origin 失敗 錯誤頁 失敗上浮 沒有 SIE、或 SIE 已過

三個數字要一起設:TTL 決定平常多久打一次 origin,SWR 決定過期瞬間還能不能零延遲賣,SIE 決定 origin 事故時還能撐多久。

互動 · stale 窗口掃描 拖三個秒數與觀察時刻,窗的邊界與每次請求的命中種類即時重算
fresh 邊緣直接賣 SWR 先賣舊的、背景更新 SIE origin 掛了仍賣舊的 同步回源,使用者排隊 窗口外 + origin 失敗
拖動 s-maxage/SWR/SIE,上面那把尺的三段寬度與下面每一次請求的命中種類會一起重算。

上尺是 age 域:上下兩條泳道分別是 origin 正常與 origin 故障時的行為,同一個 age 在兩條泳道落在不同的窗——這就是 SWR 與 SIE 的差別。下圖是時間域:每一根棒的高度是那次請求的回應延遲,圓點顏色是它落在哪個窗,▽ 是物件被重新填入的時刻。

陷阱SWR 不是「TTL 設超長」的替代寫法

把 s-maxage 設成一天,命中率確實高,但你失去了「過期後主動去看一眼」的節奏。SWR 的意義是短 TTL + 過期仍可賣:平常保持相對新,尖峰與故障時不把使用者推去排隊打 origin。兩者都設很長,等於承認這份物件可以舊很久——那就直接把 TTL 寫清楚,不要用 SWR 掩飾。

一句話答法TTL 內直接賣;剛過期用 SWR 先賣舊的再背景更新;origin 失敗用 SIE 繼續賣舊的。換到的是讀路徑延遲與故障存活,付出去的是明確的 stale 預算。
05

Cache-Control 與條件式重新驗證

邊緣是共用快取,也是 origin 的客戶端。標頭同時管「能不能存」與「過期後怎麼問」。

HTTP 篇已經區分過新鮮度(不發請求)與驗證(發請求但省 body)。這裡只補共用快取會踩到的差異:瀏覽器看到的 max-age,跟 CDN 看到的 s-maxage,常常不是同一個數字;邊緣拿著 ETag 去問 origin,跟瀏覽器拿著 ETag 去問 CDN,是兩段獨立的驗證。

共用快取真正會看的指令

指令 對 CDN 的意思 面試易混
public 允許共用快取存。有 Cookie 的回應預設常被當成 private,要明確 public 才進 CDN 不是「誰都能看這個 URL」的授權語意
private/no-store CDN 不准存。private 仍允許瀏覽器自己的快取;no-store 連瀏覽器都不該留 登入後 HTML 用 private,金融/個資用 no-store
s-maxage=N 覆蓋 max-age,只對共用快取生效 「CDN 一小時、瀏覽器一分鐘」靠它,不是靠兩份檔案
no-cache 可以存,但每次賣之前必須再驗證 名字不像「不要快取」。要禁止儲存用 no-store
must-revalidate/proxy-revalidate 過期後不准未驗證就賣。後者只綁共用快取 與 SWR 互斥:開了 SWR 就是在允許未驗證先賣
immutable 這份表徵不會在這個 URL 上變。搭配長 TTL 的雜湊檔名 用在 /app.a1b2.js,不要用在 /index.html

一條經驗:HTML 入口短 TTL 或 no-cache;雜湊資產 s-maxage 一年 + immutable;帶身分的回應不要進 CDN。

ETag 在邊緣是「origin 的驗證器」

物件過期後,邊緣帶著手上那份的 ETag 向 origin 送 If-None-Match: "v7",origin 只會有兩種回答:

origin 判定 回應 邊緣接著做 省下什麼
內容沒變(ETag 仍是 "v7") 304 Not Modified,無 body 沿用手上的物件,依新標頭重算新鮮期 origin 出口頻寬與產生成本;RTT 照付
內容已變 200 + 新表徵 + 新 ETag 換掉物件,記下新 ETag 供下次驗證 什麼都沒省,等同一次完整回源

該看「省下什麼」欄:驗證成功時省的是 body,不是往返;兩列付出的 RTT 相同。

304 的意義是省 origin 出口頻寬與產生成本,不省 RTT。邊緣到 origin 往往已經在骨幹上,RTT 不是零;所以能靠 SWR 讓使用者不排隊的,就不要讓使用者路徑去等 304。304 適合「必須確認新舊、但內容十次有九次沒變」的 API 與 HTML。

瀏覽器對 CDN 也可以再做一次同樣的對話。那是另一跳:CDN HIT 時,瀏覽器的 If-None-Match 由邊緣回答 304,origin 仍不參與。兩段驗證不要畫成一條。

關鍵CDN 終止 TLS 才能讀這些標頭

HTTP 篇提過:passthrough 的 L4 轉發看不到 URL 與 Cache-Control,邊緣快取無從開始。本篇假設 TLS 在 CDN 終止,origin 與 CDN 之間是另一段 TLS。這不是安全細節複誦,是快取能成立的前提。

一句話答法用 s-maxage/public/private 決定 CDN 能不能存、存多久;過期後邊緣拿 ETag 問 origin,304 只省 body。no-cache 是用前必驗,no-store 才是不准存。
06

主動 Purge、版本化 URL 與 Origin Shield

改了內容之後,要嘛換 URL 讓舊物件自己過期,要嘛去每個 PoP 把舊物件殺掉。回源路徑上再加一層父快取,用來收斂全球 miss。

1 · 版本化 URL(不可變資產)

建置時把內容雜湊寫進檔名:app.a1b2c3.js。這把 URL 永遠對應那一串 bytes,於是可以:

  • Cache-Control: public, s-maxage=31536000, immutable
  • 不需要 Purge。舊檔名沒人再請求,自然從 LRU 裡掉下去
  • 發布 = 上傳新檔 + 改 HTML 裡的引用。HTML 本身必須短 TTL 或 no-cache,否則使用者會拿到「舊 HTML 指向舊 hash」——這是版本化能成立的那一條繩

2 · 主動 Purge

URL 不能改時(CMS 文章、頭圖、功能開關 JSON),只能叫 CDN 把物件作廢。實作上是對 PoP 扇出的失效訊息,不是單一原子操作。

何時非 Purge 不可
同一 URL 的表徵必須立刻變
  • 緊急下架、錯誤內容、金鑰輪替後的公開設定
  • Surrogate Key/Cache Tag 讓你一次殺「這個商品的所有衍生圖」,不必枚舉 URL
  • Purge 後下一擊是 miss:搭配 Shield,避免全球 PoP 同時打 origin
代價
最終一致 + 扇出風暴
  • 各 PoP 收到 purge 的時間不同,存在短暫的新舊並存
  • 對通配符或熱 prefix 的 purge,等於人為製造一次跨 PoP 雪崩
  • API 限流、重試、冪等都要設計;purge 失敗比沒 purge 更難查

右欄三條都是 purge 的隱藏成本;決定要不要 purge 之前,先看你的失效需求能不能被左欄的 tag 模型收斂。

版本化 URL 主動 Purge
正確性 舊 URL 永遠舊、新 URL 永遠新 扇出期間新舊並存
命中率 新發布當下新檔名是冷的;舊檔名仍可命中直到沒人用 purge 當下該 key 全冷,下一波全是 miss
適用 JS/CSS/圖片等可改檔名的資產 CMS、HTML、必須穩定的 API URL
前置 建置管線產生 hash;HTML 入口不能長 TTL tag 模型、扇出 SLA、與 Shield 搭配

預設選版本化。Purge 留給「URL 就是契約」的那類物件,且把 blast radius 限制在 tag,而不是 /*。

3 · Origin Shield 與分層快取

每個 PoP 都是獨立的葉子快取。一個新資產、一次全球 purge、一個短 TTL 的熱 JSON,會讓每個未命中的 PoP 各打 origin 一次。Origin Shield 是靠近 origin 的父快取:葉子 miss 先問 Shield,Shield HIT 就結束,Shield MISS 才真正進 origin。

請求落在哪 經過的跳 origin 收到的請求
葉子 PoP HIT 葉子直接回,不經過 Shield 0
葉子 MISS、Shield HIT 葉子 → Shield 0
葉子 MISS、Shield MISS 葉子 → Shield → origin 同一物件全球合計 1 次(沒有 Shield 時是每個未命中的 PoP 各 1 次)

該看最後一欄:Shield 把「每個 PoP 各打一次」收斂成「全球打一次」,多出的那一跳只出現在 miss 的兩列。

這是跨 PoP 的 collapsing。單一 PoP 內的 in-flight 合併處理「這一棟樓的電梯」;Shield 處理「整座城市只派一輛卡車去倉庫」。多一跳的代價發生在 miss 路徑上:HIT 仍由葉子直接回,不經過 Shield。

陷阱Shield 區域掛了,miss 路徑就只剩直打或失敗

Shield 是 miss 的單點。生產上要決定 fail-open(葉子暫時直打 origin,origin 可能被瞬間打滿)還是 fail-closed(miss 變錯誤,HIT 使用者不受影響)。沒有第三種「又保護 origin 又在 Shield 死時零代價」的選項。把 Shield 放在 origin 同區域,是為了讓那唯一一跳夠短,不是為了多一個任意的快取層。

一句話答法能改檔名就版本化 + 長 TTL + immutable;URL 不能改才 Purge,並用 tag 限制範圍。跨 PoP 的 miss 收斂靠 Origin Shield,它保護 origin、把故障集中在 miss 路徑。
07

互動 Demo:URL → Cache Key 拆解器

把一條真實 URL 攤開:每一段標「進 key/被丟棄/被正規化成什麼」,再用一批帶噪音的樣本請求,算出這組規則的唯一 key 數、回源次數與命中率上限。

互動 · URL → Cache Key 拆解器 改 URL、忽略清單與 Vary 維度,逐段看誰進 key、誰被丟掉
KEEP 進 key NORM 被改寫後才進 key DROP 被丟到丟棄槽
改上面任何一個欄位,下面的 key、位元組帳與唯一 key 數都會重算。

上半的段位移圖看兩件事:打叉的段垂到丟棄槽(那些位元組沒進 key),以及保留段的連線是否交叉——交叉就代表排序正規化把它搬了位置。下半的乘法塔是碎片的來源,熱度表最後一列越長,代表碎片化的冷 key 尾巴越重。

一句話答法命中率上限是被 key 決定的:utm 與 Cookie 進 key 會把同一份物件複製成多份,Vary 每多一個維度就再乘一次。要提高命中率,先攤開 Cache Key 把不改變 bytes 的維度刪掉,而不是先把 TTL 加長。
08

邊緣運算邊界與面試折衷

Edge Compute 接在「算 key 之前或 HIT 之後」的那一層。它能改請求、能改回應,但兩頭改錯都會把快取改壞:改壞請求,第 2 節的 Cache Key 正規化白做;改壞回應,每一次 HIT 都把錯的表徵回給所有人。

邊緣運算(Workers/Functions at the edge)讓你在 PoP 上跑一小段請求/回應邏輯:驗 JWT、補地理標頭、依 Cookie 做 A/B、改圖片尺寸、擋 bot。它不是把 origin 應用搬去 300 個城市。

該放在邊緣
短、無狀態、不改變權威
  • 讀 token、拒絕明顯非法的請求,讓 miss 不必進 origin
  • 把高基數標頭正規化成低基數 Cache Key(裝置類別、登入 0/1)
  • 對已 HIT 的表徵做便宜的變換:header、HTML 注入、圖片衍生(衍生結果本身要能被快取)
不該放在邊緣
權威、長任務、每人一份狀態
  • 下單、扣庫存、寫主資料庫——300 個 PoP 沒有同一套事務
  • 用邊緣當 Redis 替代品存 session:沒有單一一致的記憶體,節點間不共享
  • 依完整 Cookie 客製 HTML 卻忘了把回應標成 private:快取會把 A 的頁賣給 B

兩欄的分界是「會不會動到權威狀態」:不動權威的正規化與門禁可以放邊緣,動權威的寫入一律留在 origin。

關鍵Compute 與 Cache 搶的是同一條命中率

在 miss 路徑上跑重計算,等於把 TTFB 加回使用者身上,CDN 白開。在 HIT 路徑上若依 Cookie 分叉回應,又沒把分叉寫進 key 或標 private,正確性先壞。設計順序是:先讓物件可快取,再用最小的 compute 去正規化 key 或做便宜變換。需要強一致讀寫的,回到 origin 與應用層快取。

面試常問 建議答法骨架
CDN 命中率上不去? 先攤 Cache Key:utm、未排序 query、Vary Cookie/UA。不是先把 TTL 加長。
TTL 到期會不會打死 origin? 單一 PoP 靠 collapsing;跨 PoP 靠 Shield。使用者路徑再用 SWR 避開排隊。應用層雪崩手段是另一層。
更新內容怎麼讓用戶立刻看到? 資產走版本化 URL;HTML/CMS 走短 TTL 或 Purge + tag。不要對雜湊檔名 purge。
s-maxage 跟 max-age 差在哪? 前者只綁共用快取。CDN 一小時、瀏覽器一分鐘,靠 s-maxage 覆蓋,不是兩份檔。
origin 掛了 CDN 還能撐嗎? SIE 窗口內可以繼續賣舊表徵;窗口外變錯誤。這是降級,不是永久備份。
邏輯要不要放到 Edge Worker? 只放正規化、門禁、便宜變換。權威寫入與 session 留 origin。Compute 若破壞 cacheability,先不要加。
差異化本篇管什麼、不管什麼

管:請求在邊緣的生命週期、Cache Key 正規化與 Vary 碎片化、HIT/MISS/PoP collapsing、SWR/SIE 的 stale 預算、共用快取視角的 Cache-Control 與 ETag 回源驗證、Purge vs 版本化 URL、Origin Shield、Edge Compute 與快取的衝突。不管:HTTP 方法/狀態碼/多路復用(HTTP/1.1 與 HTTP/2);DNS 解析與 CNAME 細節(DNS 深入解析);Cache-Aside 與 LRU/LFU(快取策略);穿透/擊穿/雪崩與 Bloom、互斥鎖(快取失效模式與雪崩防護)。流量導引只保留「快取因此變成地理分片」這一句。

邊界面試別把 Redis 失效模式答成 CDN 題

被問「CDN 如何保護 origin」時,先講 key、collapsing、SWR、Shield。Bloom Filter 與邏輯過期是應用層快取的語言;硬搬過來會讓面試官覺得你沒分清共用 HTTP 快取與自己的 Redis。

一句話答法先設計 Cache Key 與失效策略讓物件可快取,再用 SWR/SIE/Shield 保護讀路徑與 origin;Edge Compute 只做正規化與門禁,不要把權威邏輯搬去 PoP。