快取深度指南
從原理到實踐,把 FurCDN 快取調到極致;附 JSON 模式與批量匯入匯出。
從原理到實踐,把 FurCDN 快取調到極致。
TL;DR 三條黃金原則:
- 靜態資源設長 TTL + 配合版本號,讓使用者永遠拿快取
- HTML 設短 TTL,兼顧更新即時性
- API 不快取(TTL=0),動態內容必須回源
〇、JSON 模式 & 批量 JSON
跟 WAF 對齊,每條緩存規則的「新增 / 編輯」對話框增加 JSON 模式 Tab; 頁面頂部「自訂緩存規則」區段右上角新增 批量 JSON 按鈕。
JSON 模式(單規則)
儀表板 > 域名 > 編輯 > 緩存,點「自訂緩存規則」區段右上角 新增 / 任一條規則的 編輯 鉛筆。對話框頂端的 Tab:
- 表單:可視化點按填欄位。
- JSON 模式:直接編輯整條規則 JSON。
切到 JSON 後修改、儲存:存的是 JSON 內容。 切回表單時若 JSON 解析失敗會停在 JSON tab 顯示錯誤,表單內容不會被覆蓋。
批量 JSON 匯入 / 匯出
按頁面上的 批量 JSON:
- 匯出:所有「自訂」緩存規則(預設規則不匯出)序列化成 JSON 陣列,可一鍵複製。
- 匯入:貼入 JSON 陣列,逐條 POST 新增,不覆蓋既有規則。
- 與既有
(matchType, matchValue)完全相同 → 後端回 409 拒絕,計入失敗數 - 部分失敗會顯示失敗清單,成功的會留下
- 與既有
沒有「條數上限」(WAF 才有 10 條限制),你可以一次匯一打規則進去。
一、規則結構(完整 Schema)
每條緩存規則:
{
"name": "規則顯示名(必填,≤100 字)",
"matchType": "extension",
"matchValue": "jpg,png,webp,css,js",
"ttl": 604800,
"cacheControl": "public, max-age=604800, immutable",
"priority": 10,
"enabled": true
}欄位逐項
| 欄位 | 型別 | 必填 | 預設 | 說明 |
|---|---|---|---|---|
name | string | ✓ | — | 規則名(≤100 字) |
matchType | enum | ✓ | "extension" | extension / path / contentType / all |
matchValue | string | matchType≠all 必填 | — | 匹配值,語意依 matchType 而定(≤500 字) |
ttl | int 0–315360000 | ✓ | — | 秒。0 = 不快取;最大 10 年 |
cacheControl | string | null | 自訂回應的 Cache-Control,覆寫源站;留空 = 透傳源站(≤200 字) | |
priority | int ≥0 | 10 | 數字小的優先匹配 | |
enabled | bool | true | false 視為規則不存在 |
matchType × matchValue
| matchType | matchValue 格式 | 範例 |
|---|---|---|
extension | 副檔名清單,逗號分隔(可帶 . 也可不帶) | jpg,png,webp / .jpg,.png |
path | 路徑前綴或含 * 萬用 | /static/ / /api/v2/* |
contentType | 回應 Content-Type 字首萬用 | image/* / application/json |
all | (不需 matchValue) | 命中該域名所有請求 |
extension取 URL 路徑最後一個.之後的部分(不含 query)。如/img/cat.jpg?v=2→ 副檔名jpg。
後端去重
同一域名下 (matchType, matchValue) 完全相同會被拒(回 409)。
要對同一目標有不同行為,改 priority 是錯的做法 — 應該用 path + 更精準的萬用值。
二、兩級快取原理
L1:記憶體(In-Memory)
- 小檔(
<8 MiB)優先存記憶體 - LRU 淘汰
- 命中速度
<1ms - 節點重啟即清空
L2:磁碟(Disk)
- 大檔或 L1 miss 時落盤
- 單域名 / 單節點都有上限
- 命中速度 ~5ms
- 重啟保留
命中流程
請求進來
↓
WAF 評估 (allow/block/challenge)
↓
查 L1 → 命中?→ 是,X-Cache: HIT-L1 直接返回
↓ 否
查 L2 → 命中?→ 是,X-Cache: HIT-L2 + 寫入 L1 → 返回
↓ 否
回源 → X-Cache: MISS,寫 L1+L2 → 返回Response Headers
| Header | 含義 |
|---|---|
X-Cache: HIT-L1 | 命中記憶體 |
X-Cache: HIT-L2 | 命中磁碟 |
X-Cache: MISS | 未命中,剛從源站回 |
X-Cache: BYPASS | 命中但配置不快取(TTL=0) |
X-Cache-Node: <name> | 哪個節點服務了這個請求 |
X-Cache-Age: <秒> | 已快取多久 |
X-Cache-Key: <hash> | 本次的快取鍵 hash(debug 用) |
三、預設規則
新建域名時自動產生若干條 isDefault: true 規則,典型為:
| 規則 | matchType | matchValue | TTL | 用途 |
|---|---|---|---|---|
| 靜態資源 | extension | jpg,jpeg,png,gif,webp,svg,ico,css,js,woff,woff2,ttf,eot,mp4,webm,mp3 | 7 天 | 長 TTL |
| HTML 頁面 | extension | html,htm | 5 分鐘 | 兼顧更新即時性 |
| API 不快取 | path | /api/ | 0 秒 | 動態 API 必須回源 |
- 預設規則可開關但不會被批量匯出。
- 至少保留「API 不快取」,否則動態內容會被誤快取。
四、快取鍵設定(域名級)
每條規則決定 是否快取與快取多久,快取鍵組成則是域名級設定。
預設快取鍵
SHA256(method + host + uri + query + cacheKeyHeaders 們的值)ignoreQueryString
開啟後 query 不入鍵,例如版本號 ?v=20240101 對 CDN 等同同一份。
| URL | 預設 | ignoreQueryString=true |
|---|---|---|
/page | key A | key A |
/page?v=1 | key B | key A |
/page?v=2 | key C | key A |
反例:搜尋頁 /search?q=apple 跟 /search?q=banana 結果不同,不能忽略 query。
cacheKeyHeaders
陣列,加入快取鍵的 HTTP header 名稱:
["Accept-Language", "X-Device-Type"]效果:不同 header 值快取為獨立副本。
最佳場景:多語言網站根據 Accept-Language 返回不同語言。
| 請求 | 快取鍵 |
|---|---|
/page + Accept-Language: zh-TW | key_zhTW |
/page + Accept-Language: en-US | key_enUS |
快取爆炸警告
| 場景 | 後果 |
|---|---|
cacheKeyHeaders 加 User-Agent | UA 變化萬千 → 每個 UA 一份 → 快取空間爆炸 |
cacheKeyHeaders 加 Cookie | 每個 session 一份 → 同樣爆炸 |
不設 ignoreQueryString 而源站亂帶時間戳 query | 每次請求 key 都不同 → 永遠 MISS |
Header 名稱大小寫不敏感,但值大小寫敏感。
五、源站 Cache-Control 互動
源站回的 header 影響 FurCDN 怎麼快取:
| 源站 Cache-Control | FurCDN 行為 |
|---|---|
public, max-age=3600 | 快取 1 小時 |
private | 不快取(個人資料,不應在 CDN) |
no-store | 不快取 |
no-cache | 允許快取但每次回源 revalidate(目前簡化:仍按 TTL 快取) |
s-maxage=600, max-age=60 | CDN 快 10 分鐘(s-maxage 給共享快取) |
規則 vs 源站 優先級
1. 規則 ttl > 0 → 強制用規則的 TTL,完全覆寫源站
2. 規則 ttl = 0 → 強制不快取(BYPASS)
3. 沒規則匹配 → 看域名 cacheTime(預設快取時間)
4. cacheTime = 0 → 看源站 Cache-Control;源站沒設則不快取實踐:寫規則時要意識到完全覆寫源站。
六、立即刷新
域名編輯 > 「立即刷新快取」按鈕。
- master 透過 SSE 即時廣播給所有服務該域名的節點
- 每個節點同時清 L1 + L2 該域名所有 entry
- 通常
<1 秒生效 - 是全域名 purge,不能精確到單一 URL
- API 等價:
POST /api/v1/domains/{id}/purge
不要過度刷新。每次刷新後該域名所有節點全部 MISS,源站瞬時負載 ↑。 真要頻繁更新建議改 TTL 短一點,而不是手動刷新。
七、可直接貼進批量匯入的成套配方
7.1 靜態網站
[
{ "name": "HTML 短 TTL", "matchType": "extension", "matchValue": "html,htm", "ttl": 300, "priority": 10, "enabled": true },
{ "name": "JS/CSS 30 天", "matchType": "extension", "matchValue": "js,css", "ttl": 2592000, "cacheControl": "public, max-age=2592000, immutable", "priority": 10, "enabled": true },
{ "name": "圖片 30 天", "matchType": "extension", "matchValue": "jpg,jpeg,png,webp,svg,ico,gif", "ttl": 2592000, "priority": 10, "enabled": true },
{ "name": "字體 1 年", "matchType": "extension", "matchValue": "woff,woff2,ttf,eot,otf", "ttl": 31536000, "cacheControl": "public, max-age=31536000, immutable", "priority": 10, "enabled": true }
]配合「域名設定 > 忽略 Query String」即可用 ?v=hash 隨時破快取。
7.2 SPA(Single Page App)
[
{ "name": "index.html 短 TTL", "matchType": "path", "matchValue": "/", "ttl": 300, "priority": 10, "enabled": true },
{ "name": "構建產物長 TTL", "matchType": "path", "matchValue": "/static/", "ttl": 2592000, "priority": 10, "enabled": true },
{ "name": "API 不快取", "matchType": "path", "matchValue": "/api/", "ttl": 0, "priority": 5, "enabled": true }
]SPA 的 index.html 必須短 TTL,構建產物用 hash filename 可放長 TTL。
7.3 WordPress / 動態 CMS
[
{ "name": "wp-admin 不快取", "matchType": "path", "matchValue": "/wp-admin/", "ttl": 0, "priority": 1, "enabled": true },
{ "name": "wp-login 不快取", "matchType": "path", "matchValue": "/wp-login.php", "ttl": 0, "priority": 1, "enabled": true },
{ "name": "PHP 不快取", "matchType": "extension", "matchValue": "php", "ttl": 0, "priority": 5, "enabled": true },
{ "name": "圖片 7 天", "matchType": "extension", "matchValue": "jpg,jpeg,png,webp,svg,ico,gif", "ttl": 604800, "priority": 10, "enabled": true },
{ "name": "CSS/JS 1 天", "matchType": "extension", "matchValue": "css,js", "ttl": 86400, "priority": 10, "enabled": true }
]7.4 OSS / 對象儲存圖床
[
{ "name": "全部資源長 TTL", "matchType": "all", "ttl": 2592000, "cacheControl": "public, max-age=2592000, immutable", "priority": 10, "enabled": true }
]OSS 內容不變,極長 TTL。配合 ignoreQueryString=true + presigned URL,流量可以做到 99% 命中率。
7.5 即時新聞
[
{ "name": "首頁 1 分鐘", "matchType": "path", "matchValue": "/", "ttl": 60, "priority": 10, "enabled": true },
{ "name": "文章 10 分鐘", "matchType": "path", "matchValue": "/news/", "ttl": 600, "priority": 10, "enabled": true },
{ "name": "圖片 1 天", "matchType": "extension", "matchValue": "jpg,png,webp", "ttl": 86400, "priority": 10, "enabled": true },
{ "name": "API 不快取", "matchType": "path", "matchValue": "/api/", "ttl": 0, "priority": 5, "enabled": true }
]兼顧即時性 + 性能,配合「立即刷新」應對重大新聞。
7.6 多語言網站
[
{ "name": "HTML 5 分鐘", "matchType": "extension", "matchValue": "html,htm", "ttl": 300, "priority": 10, "enabled": true }
]外加域名級設定:cacheKeyHeaders=["Accept-Language"],每種語言獨立快取。
7.7 圖片 / 影片下載類
[
{ "name": "影片不上記憶體", "matchType": "extension", "matchValue": "mp4,webm,mov,mkv", "ttl": 86400, "cacheControl": "public, max-age=86400", "priority": 10, "enabled": true },
{ "name": "音訊 1 週", "matchType": "extension", "matchValue": "mp3,m4a,ogg,flac", "ttl": 604800, "priority": 10, "enabled": true }
]大檔(
>8 MiB)會自動只進 L2 不進 L1,規則不用特別寫。
7.8 規則精修:特定文件覆蓋通用規則
想 /static/v1/critical.js 走 1 小時 TTL 而其他 /static/*.js 走 30 天?
用更小的 priority 覆蓋:
[
{ "name": "Critical JS 短 TTL", "matchType": "path", "matchValue": "/static/v1/critical.js", "ttl": 3600, "priority": 5, "enabled": true },
{ "name": "其他 JS 長 TTL", "matchType": "extension", "matchValue": "js", "ttl": 2592000, "priority": 10, "enabled": true }
]八、JSON 模式常見錯誤
| 錯誤訊息 | 原因 |
|---|---|
JSON 格式錯誤: Unexpected token | 多了/少了逗號、用了 trailing comma、雙引號漏掉 |
頂層必須是規則物件 | 單規則編輯時頂層必須是 {...};陣列請走批量匯入 |
匹配類型無效 | matchType 不在 extension / path / contentType / all 之中 |
匹配值必填 | matchType 不是 all 時 matchValue 必須有值 |
TTL 不合法 | 必須是 0–315360000 的整數(10 年上限) |
已存在相同匹配規則 | 後端去重:同 (matchType, matchValue) 已有規則;改 matchValue 或刪舊的 |
九、故障排查
永遠 MISS,沒命中
- 域名 cacheEnabled 開了嗎?域名編輯 > 緩存 > 緩存總開關
- 規則 enabled=true 嗎?且 priority 沒被前面的
ttl=0規則蓋掉? - 源站
Cache-Control: no-store/private而你沒寫規則覆寫?寫規則ttl > 0強制 - query 變化:沒設
ignoreQueryString而每次 query 都不同 Vary: *:源站回了Vary: *等同不可快取
快取了不該快取的內容
- 該路徑有規則 ttl > 0 嗎?改成 0
- 登入態頁面應該有
Cache-Control: private,源站沒設就加規則 ttl=0 - 緊急刷快取:點「立即刷新」
多語言頁面只看到一種語言
- 沒設
cacheKeyHeaders=["Accept-Language"] - 第一個訪問的人是哪種語言,後面所有人都看到那種
改了源站但 CDN 還是舊內容
- TTL 還沒到 → 等 / 點「立即刷新」
- 規則
cacheControl設了長 max-age 也覆寫到 response,用戶端瀏覽器也快取了 - 改規則的 cacheControl,新請求才會拿短 max-age;舊請求等舊 max-age 過期
開了 ignoreQueryString 後 SEO 受影響?
- 不會。SEO 看的是源站 + URL 結構,跟 CDN 快取鍵無關
- 只要源站對相同 query 永遠返回同樣內容,
ignoreQueryString安全
十、進階:Cache-Control 透傳
如果你的源站本身就有完整的快取策略(例如 Cache-Control: public, max-age=86400, s-maxage=604800),不需要寫 FurCDN 規則:
- 域名
cacheEnabled=true - 域名
cacheTime設一個合理 fallback(如 60 秒) - 不寫任何 cacheRule(或只寫
path=/api/ttl=0 排除 API)
FurCDN 會直接讀源站的 s-maxage(優先) / max-age,完整尊重源站策略。
優點:單一事實來源,改源站 header 即可即時生效,不需動 CDN 設定。
還是搞不定?訪問日誌裡每筆請求都有
cache_status欄位,點開看細節:命中哪條規則、用了什麼快取鍵、源站 header 是什麼。