您的 Worker 現在可以擁有專屬的前置快取了

Dan LapidConnor Harwood

閱讀時間:20 分鐘

這篇文章亦提供 English简体中文.

BLOG-3262 hero image

今天,我們正式推出 Workers Cache:一個直接部署在您的 Worker 前方的分層快取 (Tiered Cache)。只需在 Wrangler 設定檔中加上一行,並沿用您熟悉的 Cache-Control 標頭即可完成設定。

啟用 Workers Cache 後,所有送往您 Worker 的可快取請求都會先經過 Cloudflare 的快取。如果發現新鮮的快取回應,Cloudflare 會直接回傳——您的 Worker 無須執行,您也無須為此支付 CPU 時間。若發生快取未命中 (Miss),您的 Worker 才會執行;如果回應可快取,Cloudflare 會將其儲存起來,供後續請求使用。屆時,來自全球各地的下一個請求就能直接從快取中取得內容。

BLOG-3262 image1

整個設定只需要一個設定區塊:

{
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2026-05-01",
  "cache": {
    "enabled": true
  }
}

之後,您就可以透過在回應中設定標頭,以 HTTP 原本期望的方式來控制快取:

return new Response(body, {
  headers: {
    "Cache-Control": "public, max-age=300, stale-while-revalidate=3600",
    "Cache-Tag": "products,product:123",
  },
});

而當內容變更時,您的 Worker 可以自行清除快取:

await ctx.cache.purge({ tags: ["product:123"] });

這就是完整的 API。不需要設定 Zone,不需要架設規則引擎,不需要佈建獨立的快取,也不需要登入第二個產品介面。Worker 的程式碼本身就是設定介面,且快取會跟隨 Worker 在任何執行位置運作——無論是自訂網域、workers.dev、Service Binding 後方、預覽環境,還是 Workers for Platforms 的租用戶環境中。一個 Worker,一個快取,一次設定即可。

以上是表面的 API,底層還有更多機制:橫跨我們整個網路的分層快取 (Tiered Caching)、完整支援 stale-while-revalidate 以確保舊有回應不會阻塞使用者、透過 Vary 進行內容協商、透過 ctx.props 確保多租用戶安全的快取鍵、依標籤或路徑前綴進行的程式化清除。而我們認為最大的突破在於,這個快取位於每一個 Worker 進入點的前方,而不僅僅是公開的進入點,且能針對每個進入點精準控制哪些要快取、哪些不要。最後這一點意味著您可以將快取邏輯直接編排進應用程式的結構中:打造一條由多個進入點組成的鏈路,在您需要的任何階段插入快取,並由前後的程式碼進行控制。我們將在下方逐一說明。

Workers Cache 即日起適用於所有方案下的每一個 Worker,只要在 Wrangler 中啟用即可。

這就是我們一直以來希望 Workers 能擁有的快取 API。下面將說明為何我們花了這麼長的時間、它將解鎖哪些可能性,以及接下來的規劃。

為何伺服器轉譯應用程式需要在前方設定快取

2017 年推出 Workers 時,我們主打的概念是讓您能在 Cloudflare 網路上執行程式碼,在請求送往來源伺服器的途中進行轉換。當時,Worker 位於快取與來源伺服器的前方

BLOG-3262 image5

對於當時我們所針對的使用場景而言,這是正確的架構。如果您想為每個請求新增標頭、重寫 URL、進行 A/B 分流,或在流量抵達來源伺服器前進行篩選,將 Worker 放在快取與來源伺服器前方能讓您完全掌控哪些內容被快取,哪些不被快取。客戶們確實利用這套架構打造了許多令人驚豔的產品。

但世界已經改變了。Workers 不再只是附加在來源伺服器上的配件,而是本身就成為了來源伺服器。像 AstroTanStack StartNext.jsRemixSvelteKit 這類框架,都提供了 Cloudflare Adapter,能將您的應用程式建置為 Worker。它們背後不再有傳統的來源伺服器,Worker 就是伺服器。

當 Worker 成為來源伺服器時,原本的架構就沒有東西可以快取了。每個請求都會執行您的程式碼,即便回應內容與一秒鐘前回傳的結果完全相同。Workers 執行環境的速度夠快,讓這一切得以順暢運行(它通常能輕鬆處理每秒數千萬個請求),但「快到足以渲染每個請求」仍然意味著每次頁面載入都會產生延遲,且每次呼叫都會消耗 CPU 時間。而對於伺服器轉譯應用程式來說,每次頁面載入本質上都是一次轉譯。

Workers Cache 徹底翻轉了這個架構。Cloudflare 的快取現在直接位於 Worker 的前方:

BLOG-3262 image9

發生快取命中 (Cache Hit) 時,您的 Worker 完全不會執行。Cloudflare 直接回傳快取內容,您的 CPU 計費維持為零。若發生未命中,Worker 會執行一次並填充快取;隨後,無論請求來自何處,系統都會直接從快取提供內容,而無需再次執行程式碼。

這正是 Workers 上實作伺服器端轉譯所缺少的一環。過去,開發人員只能在兩個不盡如人意的方案之間做選擇:

  • 在建置時預先轉譯所有內容(「靜態網站產生」)。頁面載入速度快,但每次變更都需要完整的重建與重新部署。對於擁有幾千個頁面的文件網站來說,這需要 5 到 10 分鐘。對於大型電子商務網站,情況則更糟糕——而且只要有任何變動,建置過程就會重新觸發。
  • 在每次請求時轉譯每個頁面。內容永遠最新,但每次頁面載入都要付出轉譯成本,每位訪客都要承受延遲。

Workers Cache 為您提供了第三種選項:隨需伺服器轉譯,快取轉譯後的回應,並依照您選擇的存留時間 (TTL) 進行重新整理。新頁面的首次請求仍會觸發渲染。而在快取過期前的後續請求,頁面將如同靜態資源一般被直接提供。當快取過期時,下一個請求會觸發重新轉譯——而有了 stale-while-revalidate,就連這一次的請求也不需要等待。

您無需建置時間即可獲得靜態網站的速度,無需高昂成本即可獲得伺服器轉譯的即時性。不需要像「增量靜態再生 (ISR)」這類特定框架的複雜機制。就只是 HTTP 快取,以它原本被設計的方式運作,並位於被設計為來源伺服器的程式碼前方。

stale-while-revalidate 是實現「即時回應」體驗的關鍵

stale-while-revalidate 指令告訴 Cloudflare:當快取回應過期時,允許它立即提供舊有副本給使用者,同時在背景重新整理回應。Cloudflare 在今年稍早已全面支援,正是這個指令將「我們會快取您的 Worker」轉化為「您的 Worker 網站感覺起來像靜態網站一樣快」。

若沒有這個指令,快取項目過期後的第一個請求,就必須等待 Worker 從頭開始轉譯頁面,使用者會明顯感受到延遲。有了它,過期後的第一個請求會立刻拿到舊頁面(並帶有 Cf-Cache-Status: UPDATING 標頭),而 Worker 則在背景執行以重新填充快取。每位使用者,包含觸發重新整理的那位,都能獲得快取等級的回應速度。

BLOG-3262 image3

實際應用上,程式碼會像這樣:

 export default {
  async fetch(request) {
    const html = await renderPage(request);
    return new Response(html, {
      headers: {
        "Content-Type": "text/html; charset=utf-8",
        // Treat as fresh for 5 minutes; serve stale for up to an hour
        // while a background refresh runs.
        "Cache-Control": "public, max-age=300, stale-while-revalidate=3600",
      },
    });
  },
};

要理解這個機制,可以參考以下心智模型:

  • 新鮮時段 (max-age):Cloudflare 直接提供快取回應,您的 Worker 不會執行。
  • 陳舊時段 (stale-while-revalidate):Cloudflare 提供快取回應,同時讓您的 Worker 在背景執行以重新整理內容。沒有任何使用者需要等待。
  • 超出兩者時段:Cloudflare 執行您的 Worker 以產生新鮮回應,而使用者需要等待那一次的轉譯。

這些時段由您決定。對於一個每幾分鐘更新一次的商品目錄,max-age=300, stale-while-revalidate=3600 意味著訪客基本上無須等待,且您的 Worker 仍會頻繁執行以維持內容新鮮度。對於一個幾乎不變更的部落格封存頁,max-age=86400, stale-while-revalidate=2592000 則意味著每個頁面每天只會執行一次 Worker。

對於全新頁面的第一個請求,是唯一需要支付完整轉譯成本的請求。此後,該頁面對訪客而言表現得像靜態輸出,而頁面產生的邏輯依然由您的 Worker 掌控。

單一 URL,多種呈現:Vary 發揮作用

現實世界的應用程式很少會對每個用戶端回傳相同的位元組。同一個商品頁面,對瀏覽器可能是 HTML,對 API 用戶端可能是 JSON。同一張影像,對支援的用戶端可能是 WebP,對不支援的則是 JPEG。同一個首頁,可能會根據使用者的不同,以英文、法文或日文呈現。

在沒有快取的情況下做到這點很簡單——您的 Worker 只需讀取請求標頭並回傳正確的內容即可。但有了快取,事情通常就會變得棘手。大多數快取系統只給您兩個糟糕的選項:針對有多種呈現形式的 URL 完全不快取,或者只快取一種呈現形式並將其傳送給所有人。

Workers Cache 支援標準的 HTTP Vary 標頭,這才是解決此問題的正確之道。當您的 Worker 回傳帶有 Vary: Accept-Encoding(或 AcceptAccept-Language 等任何請求標頭)的回應時,Cloudflare 會針對這些標頭的每種獨特組合分別儲存一個快取變體,且只會回傳儲存值與傳入請求相符的變體。

export default {
  async fetch(request) {
    const accept = request.headers.get("Accept") ?? "";
    const wantsWebp = accept.includes("image/webp");

    const body = wantsWebp ? await fetchWebpImage() : await fetchJpegImage();

    return new Response(body, {
      headers: {
        "Content-Type": wantsWebp ? "image/webp" : "image/jpeg",
        "Cache-Control": "public, max-age=3600",
        // Cache a separate variant per distinct Accept header value.
        Vary: "Accept",
      },
    });
  },
};

一個 URL,兩種快取變體。傳送 Accept: image/webp,*/* 的瀏覽器獲得 WebP。傳送 Accept: image/jpeg 的瀏覽器獲得 JPEG。兩者都來自快取。您的 Worker 在各自的第一次請求時寫入這兩種變體,此後便無需再次執行。

這是內容協商中歷經考驗的 HTTP 標準,而 Workers Cache 完全依照 RFC 9110RFC 9111 的規範實作。我們並未限制您可以針對哪些標頭使用 Vary。您只需列出所需的標頭,Cloudflare 就會根據其原始值建立變體鍵值。相關文件也詳細說明了各種邊緣案例,例如如何透過在閘道 Worker 中正規化標頭來控制變體的擴散、為何清除指令會同時使 URL 的所有變體失效,以及唯一會完全停用快取的情況 (Vary: *)。

這是您 Worker 的快取,而非區域 (Zone) 的快取

在我們探討這一切所帶來的可能性之前,有個值得點明的概念轉變。

Cloudflare 一直以來都有快取功能,但過去是在區域層級設定的:包括快取規則Cache Rules、Page Rules、可快取檔案副檔名清單、Cache Reserve、Tiered Cache 拓撲結構,以及自訂快取鍵等。所有這些都是針對每個區域設定的,而過去 Worker 必須配合該區域的配置,或是想辦法繞過它。

Workers Cache 則不同。這是您 Worker 專屬的快取——它屬於 Worker,而非某個區域。這帶來了幾項至關重要的影響:

  • 無需管理區域設定。Cache Rules、快取層級設定、檔案副檔名清單、Page Rules——這些都不適用於 Workers Cache。Worker 的 Cache-Control 標頭就是設定本身。
  • 快取跟隨 Worker,而非主機名稱。一個繫結到 api.example.comapi.example.net,並透過 Service Binding 被叫用的 Worker,在三個入口之間共用同一個快取。對 /users/42 的請求,無論從哪個途徑進入,都會命中同一個快取項目。
  • 快取可在 workers.dev 上運作。它也能在預覽 URL 中運作(每個預覽都有自己的快取,因此測試變更不會影響生產環境)。它在 Workers for Platforms 中也能運作(每個使用者 Worker 都有自己的快取,與調度器和其他租用戶隔離)。過去這些在快取方面都屬於次等公民,現在不再是了。
  • 清除作業僅限於 Worker 的入口點。當您呼叫 ctx.cache.purge({ purgeEverything: true }) 時,只會清除該 Worker 入口點的快取。不會有誤刪您區域中其他內容的風險,也不會有某個 Worker 的部署使另一個 Worker 的資料失效的風險。

您對快取的設定,都在程式碼中完成:哪些路徑使用較長的 TTL(根據路徑分支並設定不同的 max-age)、哪些請求繞過快取(回傳 Cache-Control: private)、快取鍵如何形成(控制哪些內容進入 ctx.props,調度之前在閘道 Worker 中正規化 URL)。您已經撰寫好的 Worker 本身就是設定界面。

關於這一點的詳細說明,請參閱文章《Workers Cache:您 Worker 的快取》

兩層架構,每個 Worker,無需設定

Workers Cache 預設為區域分層架構。分為兩層:

  • 下層 (Lower tier):位於距離使用者最近的 Cloudflare 資料中心。每個接收您 Worker 流量的資料中心都有自己的下層快取。
  • 上層 (Upper tier):匯集整個網路中的快取填充。這類資料中心數量較少,每個下層在未命中時都會向上層查詢。

請求首先抵達下層。若命中,則直接回傳回應,流程結束。若未命中,下層會向上層詢問。若上層命中,回應不僅會傳回給使用者,也會在回傳途中儲存到下層。只有當兩個層級都未命中時,您的 Worker 才會真正執行——而此次執行的回應會同時儲存於上下兩層。

BLOG-3262 image2

這個機制的重要性在於:全球任何一個地方的首次請求,都會填充上層快取。之後來自任何資料中心的後續請求,都能直接從上層快取取得內容,無須執行您的 Worker——即便該資料中心的下層快取從未見過此請求。相較於單層的扁平快取,這能大幅提升快取命中率,而這正是當您的 Worker 作為來源伺服器時最需要的。

這與現今區域所使用的 Tiered Cache 拓撲相同,差別在於您無需進行任何設定。沒有「為我的 Worker 開啟分層快取」的對話框。每個啟用快取的 Worker 都免費獲得分層功能。

如果您的 Worker 使用了 Smart Placement,快取也能與其完美結合:系統會先查詢快取層級,只有當兩層都未命中時,Smart Placement 才會將執行請求路由至靠近您來源伺服器的位置。關於這些層級如何互動,以及我們計劃改進的一些細節問題,我們在文件中都有更詳細的說明。

在靠近使用者靠近資料的地方執行您的應用程式

網頁效能中有一個反覆出現的兩難問題,至今沒有人能完全解決:您希望程式碼在靠近使用者的地方執行(因為使用者與伺服器之間的往返位於關鍵路徑上),同時也希望程式碼在靠近資料的地方執行(因為每次資料庫查詢也是一次往返)。選擇其中一個,另一個就會變慢。

我們多年來一直致力於兼顧兩者。我們的網路讓全球約 95% 的網際網路使用者都能在約 50 毫秒內連上。Smart PlacementPlacement Hints 讓您能將程式碼保持在靠近資料的位置,而無需考慮雲端區域。但直到現在,這兩者還無法完全協同運作。您可以選擇「靠近使用者」或「靠近資料」。若想讓應用程式的兩個部分同時處於最佳位置,您就必須是 Cloudflare 的專家。我們知道我們可以做得更好。

Workers Cache 正是填補這個缺口的最後一塊拼圖。因為快取屬於 Worker(而非區域),且因為 Service Binding 和 Worker 之間的 ctx.exports 呼叫都會經過快取,您可以將應用程式建構為一串 Worker 鏈——每個 Worker 都在它應該執行的地方執行,並以快取作為它們之間的接縫。

架構如下:

BLOG-3262 image8
  • Worker A 在靠近使用者的地方執行。它處理每個請求中低成本、對延遲敏感的部分:身分驗證、限速、路由、標頭正規化、轉譯不依賴資料的 HTML 頁面外部「殼層」
  • Worker B 在 Smart Placement 或明確的 Placement Hint 協助下,於資料附近執行。它負責繁重的工作:伺服器轉譯需要擷取資料的頁面、讀取商品目錄、產生搜尋結果、彙整 API,以及執行高成本的轉換。
  • Workers Cache 位於 Worker B 的前方。當 Worker A 透過 Service Binding 呼叫 Worker B 時,Cloudflare 會先檢查 Worker B 的快取。若命中,Worker A 直接收到回應,Worker B 完全不會執行——沒有資料中心間的跳轉,沒有資料庫查詢,也沒有轉譯工作。

快取命中的路徑變成:使用者 → 靠近使用者的 Worker A → Worker B 的快取命中 → 回應。資料跳轉的成本僅在快取未命中時才需付出。您的熱門頁面能以「貼近使用者程式碼」的速度執行,而冷門頁面在執行時仍能受惠於「貼近資料」的優勢。

您不需要為此設計任何特殊的架構。只要將應用程式寫成兩個 Worker,利用 Service Binding 將其中一個指向另一個,並在 Worker B 的 wrangler.jsonc 檔案中開啟快取,就大功告成了。

BLOG-3262 image7

預設支援多租用戶,透過 ctx.props 實現

如果您要快取一個會回傳使用者專屬資料的 Worker(例如一個針對每個登入使用者提供不同內容的 API),您需要確保某個使用者的快取回應絕不會洩漏給另一個使用者。標準解法是「不要快取經過身分驗證的請求」,而 Cloudflare 針對 Authorization 標頭的自動繞過機制正是如此。但「什麼都不快取」等於放棄了所有的效能提升。

Workers Cache 透過將呼叫者的 ctx.props 納入快取鍵來解決這個問題。當一個 Worker 透過 Service Binding 呼叫另一個 Worker,並傳遞包含使用者 ID、租用戶 ID 或其他識別碼的 ctx.props 時,擁有不同 props 的呼叫者會獲得獨立的快取項目。一位使用者的回應絕不會洩漏到另一位使用者的快取中。

import { WorkerEntrypoint } from "cloudflare:workers";

interface Props { userId: string; }

export default class Backend extends WorkerEntrypoint<Env, Props> {
  async fetch(request: Request): Promise<Response> {
    // ctx.props.userId is part of the cache key. User A and User B
    // requesting the same URL get separate cached entries.
    const { userId } = this.ctx.props;
    const data = await loadUserData(userId);

    return new Response(JSON.stringify(data), {
      headers: {
        "Content-Type": "application/json",
        "Cache-Control": "public, max-age=300",
      },
    });
  }
}

典型的模式是在閘道 Worker 中驗證請求,移除 Authorization 標頭,將已驗證使用者的 ID 設定到 ctx.props 中,然後再呼叫已啟用快取的後端 Worker。閘道會在每次請求時執行(它必須如此才能進行驗證),但昂貴的後端只有在該使用者尚無快取項目時才會執行。經過驗證的 API 從「無法快取」變成「為每位使用者安全地快取」,且快取鍵自動為您處理了隔離。關於此機制的細節,請參閱文件中的「透過 ctx.props 確保多租用戶安全」章節,以及「每位使用者的驗證回應」範例。

其他 CDN 通常迫使您在「正確性」與「命中率」之間二選一:是要根據每位使用者的權杖來設定快取金鑰,還是要將每個請求送回來源進行授權。Workers Cache 讓您能在邊緣共用快取的 API 回應,同時保留每次請求的授權邊界。據我們所知,沒有其他 CDN 將此功能作為已驗證、多租用戶 API 的內建模型提供。我們對此感到相當自豪。

橫跨每個 Worker 進入點的快取

Workers Cache 最具突破性的特性,往往容易被忽略——尤其是當您僅僅把它看作是「剛好能在 Worker 前方運作的 CDN 快取」時。

Workers Cache 位於每個 Worker 進入點的前方——包括預設匯出、每個具名的 WorkerEntrypoint,以及同一個 Worker 內透過 ctx.exports 在進入點之間的每次呼叫。最後這一項改變了您能建構的應用程式類型。

當一個進入點透過 ctx.exports 呼叫另一個進入點時,快取評估該呼叫的方式,與評估來自瀏覽器的請求完全相同。命中則回傳快取回應,被呼叫者永不執行。未命中則執行被呼叫者,並以其專屬的快取鍵儲存回應——該鍵值取決於被呼叫者的進入點、路徑、查詢字串以及 ctx.props。呼叫者雖然在每次請求時都會執行,但它移交給被呼叫者的任何工作都會被獨立地記憶化。

您可以針對每個進入點決定是否啟用快取。在您的 Wrangler 設定中,exports 對照表讓您能依名稱為每個進入點開啟或關閉快取("default" 代表預設匯出)。讓某個進入點加入快取以快取其產生的回應;讓另一個退出,使其每次請求都執行。閘道或路由器進入點(任何負責驗證、正規化或分派工作的角色)都應該退出快取,確保它們總是執行,且其本身的輸出絕不從快取提供。

這為您提供了一個可自由組合的基元。您可以將 Worker 編寫為一條由小型進入點組成的鏈路,例如驗證、正規化、路由、昂貴的讀取、資料層,並讓 Workers Cache 在您需要的任何地方插入。每個被快取的進入點都是一個記憶化單元,擁有自己的鍵值、TTL 和用於清除的標籤命名空間。任何您想設定的快取邏輯——何時執行、以什麼為鍵值、何時失效——都可以表示為普通的 Worker 程式碼:您呼叫哪個進入點、轉發什麼請求、傳遞什麼 ctx.props、設定什麼 Cache-Control

具體來說,這裡有一個單一的 Worker,它能做到在其他任何平台上都難以同時完成的三件事:對每個請求進行身分驗證、在多租用戶安全的快取鍵後方快取昂貴的後端,並在資料變更時使該快取失效。

快取是依每個進入點設定的。閘道必須在每次請求時執行,既是為了驗證,也是因為快取的閘道回應會跳過驗證檢查。因此我們在預設進入點上停用快取,僅在內部進入點啟用:

{
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2026-05-01",
  "cache": { "enabled": true },
  "exports": {
    // The gateway runs on every request — don't cache it.
    "default": { "type": "worker", "cache": { "enabled": false } },
    // Cache the expensive inner entrypoint.
    "CachedBackend": { "type": "worker", "cache": { "enabled": true } }
  }
}
import { WorkerEntrypoint } from "cloudflare:workers";

interface Env { API_TOKEN: string; }
interface Props { userId: string; }

// Inner entrypoint: the expensive work. Workers Cache sits in front
// of this — on a hit, this code never runs.
export class CachedBackend extends WorkerEntrypoint<Env, Props> {
  async fetch(request: Request): Promise<Response> {
    // ctx.props.userId is part of the cache key, so this is cached
    // separately for every user.
    const { userId } = this.ctx.props;
    const data = await loadExpensiveData(userId);

    return new Response(JSON.stringify(data), {
      headers: {
        "Content-Type": "application/json",
        "Cache-Control": "public, max-age=300, stale-while-revalidate=3600",
        "Cache-Tag": `user:${userId}`,
      },
    });
  }

  // Invalidate a user's cached response. purge() is scoped to the
  // entrypoint that calls it, so it must run inside CachedBackend —
  // the entrypoint that owns the cached response.
  async invalidate(userId: string): Promise<void> {
    await this.ctx.cache.purge({ tags: [`user:${userId}`] });
  }
}

// Outer entrypoint: runs on every request to authenticate and route.
// Caching is disabled for it in Wrangler config (above), so it always
// runs and the auth check is never skipped by a cache hit.
export default {
  async fetch(request, env, ctx): Promise<Response> {
    const userId = await authenticate(request, env);
    if (!userId) return new Response("Unauthorized", { status: 401 });

    // Invalidate this user's cache on writes, from the entrypoint that
    // owns it.
    if (request.method === "POST") {
      await handleWrite(request, userId);
      await ctx.exports.CachedBackend.invalidate(userId);
      return new Response("OK");
    }

    // For reads: strip Authorization (otherwise Cloudflare's automatic
    // bypass fires and nothing caches), then dispatch to the cached
    // backend with the authenticated user's identity in ctx.props.
    const forwarded = new Request(request);
    forwarded.headers.delete("Authorization");

    return ctx.exports.CachedBackend.fetch(forwarded, {
      props: { userId },
    });
  },
} satisfies ExportedHandler<Env>;

整個應用程式就是一個 Worker,只有一個原始碼檔案,只需一次部署。但其中包含兩個執行階段:我們僅在一個小巧的 exports 區塊中為閘道關閉快取,同時為後端開啟快取。而在它們之間橫跨著一個快取層:它依每位使用者設定快取鍵、由寫入路徑觸發失效,並在背景重新整理期間持續提供陳舊內容。這個快取階段並非事後硬加上去的配件,而是程式的一個層級,完全由程式碼撰寫而成。

這種可組合的模式擁有無限可能,相同的架構適用於以下場景:

  • 快取 Durable Object。將 Durable Object (DO) 包裝在一個進入點後方,在回應中設定 Cache-Control,一旦快取命中,讀取操作就不再需要觸及 Durable Object。寫入操作則直接送往 DO,並透過標籤清除快取。DO 本身完全無須感知快取的存在。
  • Vary 之前正規化 Accept-Encoding外層進入點從 request.cf.clientAcceptEncoding 還原原始編碼(Cloudflare 前端為了快取效率會先將其正規化),然後轉發給一個依真實值進行 Vary 的快取進入點。這能維持高命中率,同時確保用戶端拿到正確的編碼。
  • 在快取前剝離追蹤參數。外層進入點將負責 URL 標準化

或是在 ctx.exports 呼叫時透過 cf.cacheKey 設定自訂快取鍵——使得被快取的內層進入點只會看到標準化後的形式,而 ?utm_source=anything 這類參數都會折疊為單一快取項目。

組合疊加。單一 Worker 可以擁有一個負責驗證與路由的外層進入點、一個剝離追蹤參數並還原編碼標頭的正規化進入點、一個作為 Durable Object 前端的快取進入點,以及一個針對未驗證公開 API 的獨立快取進入點。它們之間透過快取階段相互連接,您無需進行複雜的設定,只需決定快取放置的位置即可。文件中的「範例」頁面詳細示範了其中幾種端到端的實作方式。

我們尚未發現其他平台能提供這種能力。CDN 快取通常位於來源站之前,而函數運算平台則專注於執行函數。我們不知道還有哪個平台能提供一種位於單一可部署單元內部、處於應用程式各元件之間的快取機制,且每個快取階段的設定都由其兩端的程式碼直接定義。這正是 Workers Cache 的獨特之處。而且,由於它能與平台現有的各項功能(如 Smart Placement、Durable Objects、Service Binding、ctx.props ctx.exports)無縫結合,您可以建立無限可能的應用模式。在這篇文章中,我們僅僅是揭開了冰山一角。

框架的全方位支援

如果您使用 Astro 進行開發,Cloudflare 轉接器會自動為您設定 Workers Cache。只需將 cacheCloudflare 提供者加入您的設定組態即可:

// astro.config.mjs
import { defineConfig } from "astro/config";
import cloudflare from "@astrojs/cloudflare";
import { cacheCloudflare } from "@astrojs/cloudflare/cache";

export default defineConfig({
  adapter: cloudflare(),
  output: "server",
  experimental: {
    cache: { provider: cacheCloudflare() },
    routeRules: {
      "/products/*": { maxAge: 300, swr: 3600, tags: ["products"] },
      "/blog/*":     { maxAge: 60,  swr: 86400, tags: ["blog"] },
    },
  },
});

該轉接器會啟用快取、在 Astro 產生的回應上設定正確的標頭、附加用於失效的 Cache-Tag 值,並提供 cache.invalidate() 輔助函式,讓您在內容變更時按標籤清除快取。選擇伺服器轉譯的 Astro 頁面會自動獲得上述「轉譯一次、進行快取、背景重新整理」的流程,無需逐一路由設定,也無需學習框架專屬的執行時期層。

我們正與其他框架的維護者合作,以推出相同的整合。如果您要為 Cloudflare 建構框架轉接器,Workers Cache API 正是您所期望的樣子:以標頭驅動的設定,程式化清除,無需建模任何平台專屬概念。

在與 Worker 相同的儀表板上查看您的快取

快取只有在您能看清其運作狀況時才有價值。現在,Workers Observability 儀表板會顯示每次叫用的快取命中資訊:

BLOG-3262 image6

您可以針對每個 Worker 查看:

  • 隨時間變化的快取命中率:這是您啟用快取後最想看到持續攀升的數字。
  • 命中 (Hits)、未命中 (Misses)、更新 (Updates)、繞過 (Bypasses) 的細項分解。如果命中率偏低,您可以在這裡找出原因——太多 BYPASS 回應(因為某個東西設定了 Cookie?)、太多 MISS 回應(快取鍵的分割範圍是否比您想的要廣?)、太多 UPDATING 回應(max-age 是否短於您的流量間隔?)。

由於所有這些資訊都與 Worker 的其他可觀測性指標(記錄、例外、CPU 時間、請求數)位於同一個儀表板上,您無需在查看區域設定和 Worker 狀態之間來回切換,即可全面瞭解當前的運作情況。

計費

快取命中不會觸發 Worker 執行,也不會產生 CPU 時間費用。不過,它們仍按標準的 Workers 請求費率計費,與其他任何叫用一樣算作一次請求。快取未命中和繞過情況則以常規方式計費——即同時收取請求費和 CPU 時間費,計費方式與不使用快取時完全相同。

結果

請求費用

CPU 時間費用

快取命中 (HIT)(Worker 不執行)

標準費率

不收費

快取未命中 (MISS)(Worker 執行)

標準費率

計費

快取繞過 (BYPASS)(Worker 執行)

標準費率

計費

靜態資產請求

標準費率

不收費

Worker 間叫用

標準費率

若 Worker 執行則計費

我們沒有為 Workers Cache 設立獨立的 SKU,也沒有每 GB 的快取儲存費。Tiered Caching、清除機制、stale-while-revalidate 以及上述的分析全部都包含在內。如果某個請求原本會執行您的 Worker,但改由 Workers Cache 以命中方式提供,您雖仍需支付標準請求費,但無須支付該請求的 CPU 時間。正因如此,快取命中的成本會低於在 Worker 中轉譯相同的回應。

有一點需要注意:當啟用快取後,原本通常是免費的請求(例如靜態資產請求,以及透過 Service Binding 或 ctx.exports 進行的 Worker 間叫用),現在都會以標準請求費率計費,因為每個請求現在都會先查詢位於 Worker 前方的快取。

下一步計畫

以下是我們明確規劃要推出的功能:

  • 利用 Smart Placement 實現更智慧的協同部署。目前,Cloudflare 會分別選擇上層快取的位置與 Smart Placement 的目標位置。在完全未命中的情況下,請求可能會在 Cloudflare 的機房之間往返兩次:一次是查詢上層快取,另一次是為了在靠近資料的位置執行您的 Worker。我們正致力於協調這些決策,讓未命中只需進行一次長距離的往返。
  • 更大的回應大小限制。在剛推出時,所有回應都遵循免費方案的快取大小限制 (512 MB),與您的帳戶方案無關。這是暫時性的——在完成幾個部署階段後,將會套用各方案標準的快取限制。
  • 更多框架整合。Astro 已經內建了與 Workers Cache 的整合。我們正與維護者合作,為其他框架新增類似的整合,包括透過 Vinext 實現的 TanStack Start 與 Next.js。
  • 將快取回應標記為過期的 API。ctx.cache.purge() 會從快取中移除符合條件的回應。我們正在研究 ctx.cache.invalidate() API,讓符合條件的回應表現為已過期,這樣下一個請求仍能透過 stale-while-revalidate 快速獲得過期回應,同時您的 Worker 在背景重新整理快取。

立即試用

Workers Cache 現已開放給所有方案上的每個 Worker 使用。

若要開始使用,只需在您的 wrangler.jsonc 中加入 "cache": { "enabled": true },重新部署,並開始設定 Cache-Control 標頭即可。Workers Cache 文件詳細介紹了所有功能面向,包含快速入門快取鍵清除機制組合模式與範例,以及除錯指南

過去,Worker 僅在快取之前執行;現在,它們也可以在快取之後執行。您可以根據需求選擇執行位置,或利用 Service Binding 同時在兩端執行。

我們期待看到您的精彩創作。