當 CDN 快取無法更新時,如何強制更新 CDN 快取

你剛部署了一項關鍵變更。源站伺服器上已經是新檔案了,但訪客看到的仍然是舊版本。這正是 cdn cache not refreshing 的典型表現。解決方法是清除各個 CDN 邊緣節點上的陳舊副本。這個過程會強制邊緣節點重新回源抓取最新資料,而且通常幾分鐘內就能完成。
出現陳舊副本是 CDN 的正常行為,並不代表伺服器壞了。每個 PoP 都會各自保留一份自己的快取版本。某個節點可能已經更新,而另一個節點還在延遲。一次 purge 請求會很快傳播到所有邊緣節點,通常幾分鐘內就能解決這個問題。
這個清除過程不需要複雜設定。你只需發出一條 purge 命令,然後驗證回傳結果即可。
CDN 中的快取失效是什麼意思
快取失效,是指通知快取:它保存的副本已經過期、不再可用。放到 CDN 裡,快取失效就是把同樣的邏輯套用到邊緣伺服器上。每個邊緣伺服器都持有自己的一份內容副本。失效操作會告訴這份邊緣副本不要再繼續提供陳舊資料。接著下一次請求就會從你的源站拉取更新後的版本。
可以把它想像成一份影本。你修改了原始文件,但影本上的文字還是舊的。複本已經和來源檔不一致了。你的邊緣節點保存的就是這份「影本」。它們會一直提供舊內容,直到有某種機制強制它們重新整理。
為什麼源站和邊緣節點會不一致
你的 CDN 邊緣快取是獨立於源站伺服器的一層。當源站內容發生變化時,邊緣快取仍然可能保留著先前抓取到的舊副本。於是,源站顯示的是更新後的檔案,而邊緣節點提供的卻還是舊版本。除非快取過期、被清除,或被強制重新整理,否則這種差異會一直存在。
在源站和使用者之間,往往還存在多層快取。非原子化部署也可能導致訪客看到「部分更新」的狀態。比方說建置錯誤時,資源更新了但資訊清單檔沒有更新,或反過來。
Purge、Invalidation 和 Eviction 的差別
這三個術語描述的是三種不同動作。Purge 指手動移除某個快取物件。Invalidation 指將某個物件標記為陳舊,使下一次請求重新拉取。Eviction 指當快取空間不足,或存活時間到期時,系統自動將物件移除。
TTL,也就是 time-to-live(存活時間),本質上是一種基於時間的驅逐觸發機制。它與基於空間的驅逐互相補充。快取滿了時,系統可能會在 TTL 尚未到期前,就先驅逐掉一些內容。一個條目一旦過期,就會變成 cache miss,從而強制回源抓取內容。
機制 | 對快取條目的影響 |
|---|---|
基於容量的驅逐 | 移除較舊條目,為新內容騰出空間 |
TTL 到期 | 在 TTL 結束後移除該條目 |
理解 cache invalidation、eviction 和 purge 三者的差別,有助於你選擇正確工具。Cache purging 是按需移除內容。Invalidation 是把內容標記為已過期。Eviction 則是自動發生的。
為什麼會出現 CDN 快取不更新
每個被快取的物件都會帶有一個過期計時器,也就是 time-to-live(TTL)。你的源站會透過 cache-control header 來設定這個計時器。TTL 如果很長,CDN 邊緣節點就會持續數小時提供舊副本。你的 cdn cache 層會把回應存放在靠近訪客的位置。cdn cache not refreshing 這個現象,往往就是從這裡開始的。
TTL、邊緣節點與陳舊副本
一個 CDN 往往橫跨數百個 PoP。每個 PoP 都會保存自己的快取副本。某個節點可能會立刻抓取到新版本,而另一個節點卻會在整個 TTL 週期內繼續保留舊版本。這種差異正解釋了為什麼 cdn serves outdated content。
TTL 的衰減表現並不完全一致,因為不同 CDN 營運商會對這些計時器做不同調校。每個邊緣節點也會在本地獨立計算計時。有些節點會提早清除陳舊條目,有些則會一直保留到真正到期。這種不均勻的時序,會讓不同地區混雜出現新舊版本。正因如此,cdn caching issues 往往很難穩定重現。只要某個節點存活時間超過預期,cdn cache not refreshing 的模式就會再次出現。
為什麼快取失效這麼難
業界一直把 cache invalidation 視為著名難題之一。不存在完美的 cache invalidation solution。從理論上說,也無法保證絕對「即時」的失效。陳舊副本會殘留,cache invalidation 的結果也始終帶有不確定性。這種時間上的不確定性,正是失效問題的核心。
而分散式 CDN 會進一步放大這個難度。Cache invalidation 需要讓數百個邊緣伺服器同時刪除內容。在刪除傳播過程中,正在進行中的請求仍可能抓到舊副本。大量回源重抓還會觸發 thundering herd(驚群)問題。重新抓取到的物件又可能引發下一輪失效波動。每一次 invalidation 請求都帶有傳播成本。要在成千上萬個節點之間協調失效,本身就是問題的本質。Purge 的時效性,也同樣帶有這種 invalidation 的不確定性。
可用的快取失效策略主要有三種:
等待 TTL 到期:快取副本會在過期後自動更新,但對緊急更新來說,這個辦法並不適合。
發起明確的 purge:透過 API 在所有邊緣節點上刪除對應條目。這個過程通常需要幾秒到幾分鐘。
修改 URL:例如使用
logo-v2.jpg這樣的新位址,從根本上繞過 invalidation。然後在 HTML 中更新所有引用。
長期存在的 cdn caching issues 往往需要雙管齊下。對日常內容,要調校 ttl 和 cache-control headers。對熱修復情境,則保留 cache purging。Invalidation 不是一個簡單開關,而是一種取捨。你應該把 invalidation 當作一個明確的操作來對待。按需 purge 能帶來控制力;但頻繁 purge 則會浪費資源。合理的 cache invalidation 能幫助降低 源站流量。手動 cache invalidation 更適合在低流量時段進行。有效的 invalidation,核心是在內容新鮮度與成本之間取得平衡。
如何強制重新整理內容
你可以採用多種方式來強制重新整理內容。建議先從手動 purge 流程開始,再選擇適合自己技術堆疊的方法。
依 URL 或標籤清除 CDN 快取
手動 purge 通常從服務商控制台開始。你打開快取面板,輸入要移除的檔案路徑,然後確認執行。之後邊緣節點會捨棄這些物件,並在下一次請求到來時重新回源抓取。
Azure CDN 在其控制台中提供了 Purge 按鈕。你可以輸入像 /images/* 這樣的萬用路徑,一次清除整個目錄。Cloudflare 既支援在控制台中手動 flush,也支援透過 purge api 實現自動化。你可以用一次 API 呼叫來 purge cdn cache by URL:
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
-H "X-Auth-Email: $EMAIL" \
-H "X-Auth-Key: $APIKEY" \
-H "Content-Type: application/json" \
--data '{"files":["https://example.com/cf-logo-on-white-bg.svg"]}'請求完成後,檔案狀態會從 cf-cache-status: HIT 變為 cf-cache-status: MISS。這個變化表示你成功只清除了目標 URL。
可透過指定 URL 的方式,精細化地從 Cloudflare 快取中移除一個或多個檔案。所有方案都支援依 URL 清除。
全域 purge 會一次清空所有內容。它是最快的方式,但也是影響最大的一種。它會迫使所有邊緣節點重新抓取全部內容,從而顯著增加源站負載。相比之下,依 URL 或標籤 purge 更精細。你只移除發生變化的內容,其餘快取保持不動。
版本化 URL、TTL 調整與 Surrogate Keys
版本化 URL 可以徹底繞過 invalidation。常見方式有兩種:檔案指紋與查詢參數。檔案指紋是把雜湊寫進檔名裡,例如 app.9f3c2.js。查詢參數則是在 URL 後附加版本號,例如 style.css?v=42。這兩種方式都會讓資源擁有一個新位址,因此不需要進行 cache purging。
Surrogate keys 則提供了另一條路徑。它們通常是附加在快取回應上的 HTTP headers,例如 x-surrogate-key,用來為內容加上標籤。內容更新時,你的後端無須依整個 URL 去 purge,而是可以針對特定 key 發起精準的 BAN 請求。快取會把這些標籤保存在記憶體中,並在所有相關路由上即時讓對應快取物件失效,而不需要重新整理整個快取。這種方式尤其適合你在設定 CDN 邊緣快取以進行精準失效時,以及在高流量、讀取密集型 API 情境下做全球擴展最佳化時使用。
順帶提醒一下:瀏覽器快取和 CDN 快取是兩回事。打開 DevTools,進入 Network 分頁,保持「Disable cache」不勾選,篩選 Doc,然後重新整理頁面。你看到的將是瀏覽器自己保存的內容。不要把這一層和邊緣快取層混淆。
傳播、自動化與最佳實務
等待邊緣節點完成同步
Purge 請求並不會在同一時刻到達所有邊緣節點。它會隨著時間在整個 CDN 網路中逐步傳播。你可能會在某個地區看到新內容,而另一個地區仍在提供舊版本。這種延遲叫作傳播延遲,並不代表 purge 失敗。Cache invalidation 傳播到所有節點所需的時間,會因服務商不同,以及持有該物件的節點數量不同而有所差異。
在真正依賴某家 CDN 的 purge 能力之前,先測試它的典型傳播時長。然後在你的工作流程中,加入與你測得結果相匹配的等待時間。如果跳過這一步,你就可能把正常的傳播延遲,誤判為 cdn cache not refreshing。接著你又會再次發起 purge,結果只是增加負載,而不會真正解決問題。很多 cdn caching issues,與其說是靠重複 purge 解決,不如說是靠耐心和驗證解決的。
把 Purge 接入部署流程
應當在開始部署之前先 purge CDN 快取,而不是在部署完成之後。這樣的順序可以避免訪客在上傳期間命中陳舊內容。盡量使用選擇性 purge,針對具體檔案、目錄或 URL 模式操作。這樣未發生變化的內容仍能保留快取帶來的效能收益。像 /css/* 或 /js/* 這樣的萬用 purge,在你一次更新同一目錄中的大量相關檔案時會很好用。
你可以透過部署腳本中的 purge api 來自動化這一步。把 purge 命令納入發佈流程,等待確認結果,為失敗請求加入重試邏輯,並記錄全部日誌。再將 purge 與檔案指紋、查詢字串版本號這類版本化策略結合起來。這樣更新後的資源會直接繞過舊快取條目。採用原子化部署也很關鍵,例如先上傳到帶版本號的目錄,再統一切換引用,這樣就能消除新舊資源混雜出現的空窗期。
大多數反覆出現的問題,都來自三個錯誤。只 purge 單個 URL,而整個資源包其實都發生了變化,會導致大部分資源仍然陳舊。忘記輪替 API token,會讓自動化靜默失效。熱修復情境下完全依賴 TTL,則會讓使用者在數小時內繼續看到舊內容。應當把 cache invalidation best practices 視為流程中的必要環節,而不是事後補救。部署後,還要跨多個邊緣地區測試快取行為,並在測試時清理瀏覽器快取。養成這些習慣後,cache invalidation 就會從反覆爆發的緊急故障,變成一項日常流程。
Purge 能強制重新整理。預防措施則能避免問題再次發生。你可以先選一種「強制更新」方法開始:緊急修復時使用 API purge,靜態資源則優先使用版本化 URL。版本化 URL 更適合作為主要防線,因為檔名一旦變化,就會建立新的快取條目,不再需要讓舊條目失效。然後再增加一種預防習慣:在每次發佈前,把 purge 接入部署流程。把 API purge 自動化視為一種次級、回應式工具。只要這兩種做法協同運作,CDN 就不會再頻繁「給你驚喜」。陳舊內容是常見的 CDN 問題,它有明確的解決方式,並不神祕。
常見問題
為什麼我 purge 之後,CDN 仍然在提供舊檔案?
Purge 請求會隨著時間逐步傳播到各個邊緣節點。某個地區可能已經顯示新內容,而另一個地區還保留著舊副本。這種差異是傳播延遲,不代表 purge 失敗。先等待請求完成,再檢查回應標頭進行驗證,然後再決定是否需要再次 purge。
一次 CDN purge 需要多久才能傳播到所有邊緣節點?
具體時間取決於服務商,以及持有該物件的節點數量。在真正依賴它之前,先測量你的服務商通常需要多長時間。然後把這個等待時間納入工作流程中。否則,你很容易把正常傳播延遲誤判為快取拒絕更新。
我應該清空全部快取,還是只清除我改動過的檔案?
全域 purge 會一次清空所有內容。它雖然是最快的方法,但會迫使所有邊緣節點重新抓取全部內容,從而增加源站負載。依 URL 或標籤 purge 更加精細。只移除發生變化的內容,把其他快取保留下來。
版本化 URL 能否完全取代 purge?
版本化 URL 會給每個資源分配一個新位址,因此這些檔案不需要 purge。檔案指紋和查詢參數都屬於這種做法。不過,API purge 仍然應該保留,作為那些來不及改檔名的緊急修復情境下的備援手段。
為什麼 purge 成功了,我看到的頁面還是舊的?
因為你的瀏覽器也有自己的快取層,它和 CDN 邊緣快取是分開的。打開 DevTools,進入 Network 分頁,保持「Disable cache」不勾選,篩選 Doc,然後重新整理頁面。這樣你看到的就是瀏覽器自己快取了什麼。不要把瀏覽器快取和邊緣快取混為一談。
