8.2 快取策略精修#
本節目標#
- 掌握 Cache-Control 語意與
stale-while-revalidate的時間數學。 - 會在 Worker 內使用 Cache API(
caches.default),與 CDN 邊緣快取分工。 - 使用 Cache Rules 與 Purge Cache(手動與 API)管理失效。
- 理解邊緣快取底層的 Bypass / Respect origin 兩種行為模式差異。
快取的經濟學回顧#
1.1 節說過:CDN 的價值來自「多數請求不回源」。量化它:
\[ \text{回源率} = 1 - \text{命中率} \quad\Rightarrow\quad \text{TTFB}_{\text{均}} = h \cdot t_{\text{hit}} + (1-h)(t_{\text{miss}}) \]
命中率 \(h\) 從 0.85 提升到 0.95,看似只差 10%,但「未命中」才是延遲與源站壓力的大頭——因為未命中往往伴隨驚群效應(thundering herd):同一 URL 同時湧入 50 個查詢,全部穿透回源。這就是本章要「精修」的原因。
Cache-Control:標準語意一覽#
Cache-Control: max-age=300, s-maxage=86400, stale-while-revalidate=600, stale-if-error=86400
| 指令 | 對象 | 意義 |
|---|---|---|
max-age | 瀏覽器 | 客戶端快取多久 |
s-maxage | 共享快取(CDN) | 覆蓋 max-age(CDN 可存更久) |
stale-while-revalidate | 兩者 | 過期後可直接回舊的,同時背景驗證 |
stale-if-error | 兩者 | 源站掛掉時可繼續回舊資料 |
stale-while-revalidate 的時間線#
這是最值得投資的一行設定:使用者永遠拿到即時響應(哪怕稍舊),新鮮度在「無感」中完成。
t=0 ─→ 回應新鮮(max-age 內)
t=300 ─→ 過期。第 1 個請求回 stale 舊檔 + 背後觸發 revalidate
t=302 ─→ 快取已是新版;600 秒內的後續請求都瞬間回應
t=1500 ─→ SWR 窗口都過了 → 才真正同步回源
\[ \text{使用者感知延遲} \approx \begin{cases} t_{\text{hit}} & \text{max-age 內或 SWR 窗口內} \\ t_{\text{miss}} & \text{全過期(少數)}\end{cases} \]
靜態資源(JS/CSS 帶 hash)可以放心 max-age=31536000, immutable,因為檔名即版本,失效問題被 URL 命名解決——這是工程上最便宜的失效策略。
三層快取架構:瀏覽器、邊緣、Worker 內#
前面章節部署的 Workers 屬於「動態程式」,但它自己也能用 Cache API 再快取一層(例如上游 API 的 JSON):
export default {
async fetch(req: Request, env: Env, ctx: ExecutionContext) {
const url = new URL(req.url);
const cache = caches.default;
let res = await cache.match(req);
if (!res) {
// cache miss:回源(假設是 D1 查詢組裝的結果)
res = await buildResponse(url); // 動態產生
res = new Response(res.body, res); // Response 可能不可變,複製一份
res.headers.set("Cache-Control", "s-maxage=300, stale-while-revalidate=600");
// 注意:cache.put 只在回源執行緒做,且 206/串流有部分限制
ctx.waitUntil(cache.put(req, res.clone()));
res.headers.set("X-Cache", "MISS");
} else {
res = new Response(res.body, res);
res.headers.set("X-Cache", "HIT"); // 選擇性回報給自己觀測(8.1)
}
return res;
}
} satisfies ExportedHandler<Env>;
三層的分工原則:
| 層 | 誰控制 | 典型 TTL |
|---|---|---|
| 瀏覽器快取 | max-age / 服務 Worker | 靜態資源長、動態 API 用 no-store |
| 邊緣 CDN 快取 | s-maxage + Cache Rules | 頁面/媒體數分鐘~數月 |
| Worker 內 Cache API | 你的程式碼 | 上游資料的微小副本 |
Cache Rules:宣告式控制#
希望你不必寫程式就能影響 CDN 層的快取行為:Dashboard → Caching → Cache Rules(2022 起取代 Page Rules)。
範例規則:hostname eq cdn.example.com and path matches "^/thumb/.*" →
- Cache Eligibility:Eligible for cache
- Edge TTL:Override origin,設 7 天
- Browser TTL:Respect origin
- Query String:忽略所有 query 參數(圖片縮圖很適合)
Bypass Cache vs Respect origin:同一個規則的兩種靈魂#
Cache Rules / 底層快取對「要不要遵守源站 Cache-Control」的態度不同:
| 模式 | 作用 | 使用時機 |
|---|---|---|
Bypass cache | 該請求完全不進快取,每次回源(也不寫快取) | API、帶身分 Cookie 的動態頁 |
Respect origin(預設) | 看源站怎麼說:Cache-Control: no-store 就不存;有 s-maxage 就以它為準 | 你能控制源站 header 時(推荐,職責單一) |
| Override origin | 優先於源站 header | 源站你動不了(第三方平台)時 |
問題的本質:快取決策要放在「知道資料新鮮度語意的程式碼」還是「基礎設施的設定」?經驗法則:能用 origin header 控制(Respect origin)就用它,讓 Worker 的 header 設定成為唯一真相;只有「源站不受你控」時才用 override。
Purge Cache:失效的兩條路#
1.6 節介紹過 Scheduled Cache 的概念,實務上你有兩個入口:
# 手動(Dashboard 或本例的 API)
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"purge_everything": true}'
# 指定檔案(更快,影響面小)
curl -X POST ".../purge_cache" -H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"files": ["https://example.com/index.html", "https://example.com/style.css"]}'
重要觀念:
- Cache API(Worker 內)與 CDN 邊緣快取是兩套不同的儲存。Purge 只清 CDN 層;你若
cache.put過,要用cache.match + 不回存(或檔案層級控制)自己管理——這是初學最常踩的坑。 purge_everything生效在數秒內,但全站會瞬間變成 100% miss,回源壓力突然放大,建議挑離峰時段執行。
小結與練習#
重點回顧
- 命中率的小幅提升可以大幅降低未命中的回源成本;SWR 讓過期帶來「零延遲退路」。
- 三層快取各司其職:瀏覽器(帶 hash 的資源存久)、邊緣 CDN(s-maxage + Cache Rules)、Worker 內 Cache API(獨立儲存,要自己管失效)。
- 快取失效有兩種模式:URL 命名(最便宜)與 Purge(必要時)。
- Bypass = 完全不碰快取,Respect origin = 源站 header 說了算。
練習
- 給一個由 Worker 提供的 HTML 頁與一個帶 hash 的 JS 檔,設計各自的 Cache-Control,並解釋為什麼 HTML 不適合長
max-age。 - 在 Worker 中實作
cache.put用正規化 URL(去掉不影響內容的 query)當 key,量測命中率提升。 - 試著推一次
purge_everything,並觀察接下來 10 分鐘內 8.1 節 Observability 的 CPU 曲線變化,說明為什麼 purge 會造成算力尖峰。