2026/9/25 10:22:20

@cloudflare/workers-response-store 深入解析:在 Cloudflare Workers 上构建程序化响应缓存

@cloudflare/workers-response-store 深入解析:在 Cloudflare Workers 上构建程序化响应缓存 后端Web框架SSR【免费下载链接】vinextVite plugin that reimplements the Next.js API surface — deploy anywhere项目地址https://gitcode.com/gh_mirrors/vi/vinext点击查看免费下载cloudflare/workers-response-store是一个与框架无关的程序化响应存储库用于在 Cloudflare Workers 中构建持久化响应缓存、stale-while-revalidateSWR过期再验证、按需刷新与清理purge能力。它把 Workers Cache边缘交付、R2响应体存储与 SQLite Durable Object强一致的元数据、版本与标签失效组合成一个统一的编程接口既可以作为独立的缓存 Worker 通过 service binding 被多个应用复用也可以与业务逻辑共存于同一个 Worker 中。读完本文你将掌握两种部署模式的完整配置、fetch/put/refresh/purge四大 API 的语义、缓存键与新鲜度规则以及版本保留、分片扩展等运维要点。本文以 packages/workers-response-store/README.md 为骨架并补充仓库源码中的实现细节作为佐证。背景为什么需要程序化响应缓存Cloudflare 的原生 HTTP 缓存由缓存规则驱动而很多框架如 Next.js 适配器、自定义 SSR 服务需要在代码层面精确控制哪些响应可以缓存、如何失效、过期后如何重建。Workers Response Store 正是为这类场景设计的响应持久化把渲染结果连同响应头、状态码完整写入 R2即使边缘缓存被逐出也能从 R2 重建强一致元数据用 SQLite Durable Object 记录每个缓存键的活跃版本、新鲜度窗口与标签保证并发写入、清理与再验证之间的顺序一致程序化控制提供fetch / put / refresh / purge等显式方法让框架适配层可以自主决定缓存策略而不是依赖隐式的 CDN 规则。在仓库中这个包位于 packages/workers-response-store其 package.json 声明了主入口dist/index.js与./service服务入口并提供单 Worker 与 service-binding 两套可运行示例example/目录。安装与前置条件npm install cloudflare/workers-response-store使用前需要准备以下环境示例配置均采用当前兼容日期compatibility date 与 flag配置nodejs_compat兼容标志如compatibility_date: 2026-09-16R2 存储桶部署前先创建配置中指定的 bucket示例中为CACHE_BODIES绑定生成类型在 Wrangler 配置完成后运行wrangler types生成Env类型包含 R2、Durable Object、service binding 与版本元数据绑定可观测性示例配置开启observability便于观察缓存命中率与再验证行为。两种部署模式总览模式适用场景特点Service-binding 模式独立应用或多个 Worker 共享同一套缓存基础设施缓存 Worker 独立部署与观测存储绑定不进入应用 Worker后续可无感复用同一缓存服务单 Worker 模式一个部署、一份 Wrangler 配置更合适应用、缓存入口、R2 与元数据 Durable Object 全部放在同一部署中无论哪种模式二者暴露的读、写、刷新、清理 API 完全一致对应源码中的WorkersResponseStore接口见 src/binding.ts。模式一Service-binding 部署Service-binding 模式把缓存基础设施与应用代码分离缓存 Worker 独享 Workers Cache、R2 与 SQLite Durable Object每个应用 Worker 各自持有自己的重新生成regenerate回调并把该版本钉住的能力通过 RPC 传递给缓存 Worker对应 src/index.ts 中从CF_VERSION_METADATA.id读取版本号并构造 invocation 的实现。1. 缓存 Worker将main指向安装包自带的服务入口dist/service.js{ $schema: ./node_modules/wrangler/config-schema.json, name: response-store, main: ./node_modules/cloudflare/workers-response-store/dist/service.js, compatibility_date: 2026-09-16, compatibility_flags: [nodejs_compat], workers_dev: false, cache: { enabled: true }, exports: { default: { type: worker, cache: { enabled: false } }, ResponseStoreBinding: { type: worker, cache: { enabled: true } }, CacheMetadata: { type: durable-object, storage: sqlite }, }, r2_buckets: [{ binding: CACHE_BODIES, bucket_name: my-response-bodies }], durable_objects: { bindings: [{ name: CACHE_METADATA, class_name: CacheMetadata }], }, observability: { enabled: true }, }关键点解读exports声明了三个入口default未开启缓存供 RPC 服务入口使用、ResponseStoreBinding开启 Workers Cache实际的读写路径、CacheMetadata声明式 SQLite Durable ObjectCacheMetadata是声明式导出不要再把它加进 Wrangler 的migrations——这一点在 README 与示例 example/service-binding/wrangler.cache.jsonc 中均有强调存储绑定R2 bucket、Durable Object namespace全部留在缓存 Worker 侧应用 Worker 无需接触存储。2. 应用 Worker把应用绑定到未开启缓存的ResponseStoreService入口并附带版本元数据{ $schema: ./node_modules/wrangler/config-schema.json, name: my-app, main: src/worker.ts, compatibility_date: 2026-09-16, compatibility_flags: [nodejs_compat], services: [ { binding: RESPONSE_STORE, service: response-store, entrypoint: ResponseStoreService, }, ], version_metadata: { binding: CF_VERSION_METADATA }, observability: { enabled: true }, }version_metadata绑定是必需项——源码 src/binding.ts 中getVersionId()明确要求存在版本 ID否则抛出Workers Response Store requires a version_metadata binding。版本 ID 用于把 R2 对象与元数据 Durable Object 按应用版本隔离见后文版本保留与清理。3. 创建客户端并导出入口import { createWorkersResponseStoreClient } from cloudflare/workers-response-store; const responseStore createWorkersResponseStoreClientEnv({ regenerate(input, { env, ctx }) { return render(input.request, env, ctx, input); }, }); export const { ResponseStoreRevalidator, ResponseStoreClient } responseStore.entrypoints;createWorkersResponseStoreClient会生成两个入口ResponseStoreRevalidator承载应用自己的 regenerate 回调与ResponseStoreClient把fetch/put/refresh/purge通过 RPC 转发给缓存 Worker 的ResponseStoreService。仓库中的完整示例见 example/service-binding/user-worker.ts对应的两份 Wrangler 配置分别为 wrangler.cache.jsonc 与 wrangler.user.jsonc。部署顺序为先部署缓存 Worker再部署应用 Worker——没有反向 service binding也不存在循环部署。模式二单 Worker 部署单 Worker 模式适合一个部署、一份配置足够用的场景。下面三个步骤构成最小集成闭环。1. 创建并导出 storeimport { createWorkersResponseStore } from cloudflare/workers-response-store; const responseStore createWorkersResponseStoreEnv({ regenerate(input, { env, ctx }) { return render(input.request, env, ctx, { id: input.id, args: input.args, reason: input.reason, }); }, }); export const { CacheMetadata, ResponseStoreRevalidator, ResponseStoreBinding } responseStore.entrypoints;命名导出必须与 Wrangler 配置中的声明一致CacheMetadata、ResponseStoreRevalidator、ResponseStoreBindingregenerate会在四种场景被调用SWR 过期、条目硬过期、R2 响应体缺失、显式刷新每个存储的响应都要附带一个可序列化的 revalidator 描述符{ id, args }回调才能在未来重建它。SerializableValue类型null | boolean | number | string | 数组 | 对象定义在 src/binding.ts。2. 读穿回填function isResponseStoreMiss(response: Response): boolean { return response.status 404 response.headers.get(X-Workers-Response-Store) MISS; } export default { async fetch(request, env, ctx) { if (request.method ! GET) { return render(request, env, ctx); } // Cache identity is pathname query string. Use a canonical GET request // with only information that is safe to share between visitors. const cacheRequest new Request(request.url); const cached await responseStore.fetch(cacheRequest); if (!isResponseStoreMiss(cached)) { return cached; } const response await render(request, env, ctx); await responseStore.put(cacheRequest, response.clone(), { revalidator: { id: page, args: [] }, }); return response; }, } satisfies ExportedHandlerEnv;这是最小集成循环先fetch读缓存命中直接返回未命中404 X-Workers-Response-Store: MISS则渲染并把结果put进去。框架可以在此基础上叠加自己的可缓存性规则、vary 维度、流式策略、错误处理以及从路由到 revalidator id/args 的映射。3. 配置 Wrangler{ $schema: ./node_modules/wrangler/config-schema.json, name: my-app, main: src/worker.ts, compatibility_date: 2026-09-16, compatibility_flags: [nodejs_compat], cache: { enabled: true }, exports: { default: { type: worker, cache: { enabled: false } }, ResponseStoreBinding: { type: worker, cache: { enabled: true } }, CacheMetadata: { type: durable-object, storage: sqlite }, }, r2_buckets: [{ binding: CACHE_BODIES, bucket_name: my-response-bodies }], durable_objects: { bindings: [{ name: CACHE_METADATA, class_name: CacheMetadata }], }, version_metadata: { binding: CF_VERSION_METADATA }, observability: { enabled: true }, }同样地CacheMetadata是声明式 SQLite Durable Object 导出不要重复加入migrations部署前创建 R2 bucket配置完成后运行wrangler types生成Env。仓库中的可运行单 Worker 示例见 example/worker.ts其regenerate实现还展示了 revalidator 如何读取input.args中的参数如delayMs、failOnce、cacheControl来控制再验证行为。完整 API 参考createWorkersResponseStore()与createWorkersResponseStoreClient()返回的对象实现了同一套 API方法行为fetch(request)读取规范化的GET缓存键。返回存储的响应或404缓存未命中。SWR 窗口内的过期条目立即返回并在后台重新生成硬过期条目等待重新生成完成。put(request, response, options?)在规范化GET缓存键下存储响应。options.revalidator提供{ id, args }供未来重新生成当替换可能已存在于 Workers Cache 中的条目时设置purgeExisting: true。refresh({ tags, pathPrefixes })重新生成匹配条目并清理其先前的边缘响应。至少需要一个选择器。purge({ tags, pathPrefixes, purgeEverything })移除匹配元数据、记录标签失效、删除响应体并清理对应边缘响应。至少需要一个选择器或purgeEverything: true。getTagExpiration(tags)返回一组框架管理的软标签的最新失效时间戳。大多数集成不需要这个底层方法。变更方法统一返回type ResponseStoreMutationResult { backingStoreUpdated: boolean; edgePurgeAccepted: boolean; };该类型定义见 src/binding.ts。另外put还有一个内部选项coalesce见 ResponseStorePutOptions用于合并同一缓存键的并发框架写入。缓存键规则键必须是GET请求源码 deriveCacheKey 会直接抛出Workers Response Store keys must be GET requests身份由URL pathname query string组成scheme 与 host 被忽略随后对cacheKey做 SHA-256 得到keyHash用于分片路由与对象命名默认情况下每个键的响应与读取元数据一起存于单个版本作用域的 R2 对象中因此新鲜命中与未命中不会查询元数据 Durable Object无需特殊键格式键应基于可信路由与 vary 数据构建不要包含任意访问者请求头或无界输入除非你刻意创建独立的共享响应。新鲜度与标签新鲜度按优先级从Cloudflare-CDN-Cache-Control→CDN-Cache-Control→Cache-Control推导。这一优先级顺序与实现一一对应src/cache-policy.ts 的deriveCachePolicy()依次读取这三个头。实现细节还包括no-store/private直接禁止缓存存储s-maxage/must-revalidate/proxy-revalidate禁止过期服务cache-policy.tsmax-age优先取s-maxage其次max-age并减去传入的Age值作为初始年龄stale-while-revalidate决定 SWR 窗口swrUntil freshUntil staleWhileRevalidate * 1000store 会保留传入的Age值并在存储期间持续推进它representationAge。在传给put()的响应上设置Cache-Tag头逗号分隔即可关联清理标签。refresh()与purge()也接受 pathname 前缀pathPrefixes。标签失效时大小写不敏感匹配。regenerate回调收到存储的id与args、一个规范化缓存键请求以及下列 reason 之一Reason触发时机swr过期响应在其 SWR 窗口内被返回。expired响应已越过 SWR 窗口读取必须等待。missingR2 对象存在但其已提交的响应内容不可用。manualrefresh()选中了该条目。RevalidationReason联合类型定义见 src/binding.ts。协议响应头fetch()返回的响应包含以下 Response Store 协议头头值含义X-Workers-Response-StoreMISS、BLOB-FRESH或BLOB-STALE绑定读取时元数据缺失或 R2 响应处于新鲜/过期状态。未命中是带Cache-Control: no-store的404。X-Workers-Response-Store-Revision正整数返回给调用方的当前活跃存储版本。未命中时缺失。X-Workers-Response-Store-Binding-InvocationUUID标识从 R2 加载响应的那次绑定调用。Workers Cache 会复用其缓存填充时的值可用于诊断绑定是否实际运行。未命中时缺失。X-Workers-Response-Store-Age-Basiscreated-at-ms:initial-age-seconds响应创建时间戳与其原始Age。当前年龄按initialAgeSeconds max(0, floor((Date.now() - createdAtMs) / 1000))计算。这些头的生成逻辑见 createStoredResponse 与 MISS_HEADERS。注意这些头描述的是 Response Store 绑定的结果而非当前 Workers Cache 边缘状态——边缘状态请查看CF-Cache-Status。适配层可以在返回应用响应前消费并移除这些协议头。性能设计Workers Cache 命中即短路Workers Cache 命中会完全绕过Response Store 绑定 Worker、R2 与所有元数据 Durable Object。只有当 Workers Cache 因未命中或更新而调用绑定时backing store 与分片路径才会运行。这正是exports中ResponseStoreBinding开启cache而default关闭的原因。元数据分片opt-inconst responseStore createWorkersResponseStoreEnv({ shards: 16, regenerate, });shards必须是大于 1 的整数校验逻辑见 validateResponseStoreShardsNumber.isSafeInteger(shards) shards 1否则抛TypeError未设置时所有版本作用域元数据使用最初的单个 Durable Object每个缓存键及其全部版本、claims、pending 对象都确定性路由到单个分片基于keyHash前 8 位十六进制取模见 getMetadatarefresh与purge在所有分片间扇出标签失效时间戳会被复制到每个分片从而保证 publication fencing 与软标签检查仍然正确软标签读取会选中稳定副本以分散负载getTagMetadata改变分片数量会选定新的元数据与 R2 布局应视为缓存冷启动式部署变更而不是原地扩缩容。元数据位置提示两种部署模式都接受 Durable Object 位置提示const responseStore createWorkersResponseStoreEnv({ locationHint: weur, regenerate, });service-binding 模式下对createWorkersResponseStoreClient()使用相同的locationHint选项。合法的取值源码 RESPONSE_STORE_LOCATION_HINTS 定义并校验包括afr、apac、apac-ne、apac-se、eeur、enam、me、oc、sam、weur、wnam。该提示是best-effort的只影响每个元数据 Durable Object 的首次创建。已有对象在选项改变时永不迁移因此应把修改后的提示作为缓存冷启动部署推出并使其与 R2 bucket 的位置一起规划。存储与运维行为一次请求的完整路径如下application Worker └─ ResponseStoreBinding (Workers Cache enabled) ├─ cache hit ───────────────► stored Response └─ cache miss ├─ response read metadata ─► R2 ├─ write coordination/revisions ─► SQLite Durable Object └─ regeneration ───────► application callbackR2 对象布局与一致性响应使用runtime-cache/version-id/r2-v1/[shards-count/]digest/active布局objectKeyRoot 与 r2ObjectKey。布局段r2-v1将本实现与 service-binding 回滚、滚动部署期间更旧的缓存 Worker 版本隔离SQLite 修订号与条件发布防止慢写入覆盖新写入或复活已清理条目。用户 RPC、R2 与缓存清理 I/O 都在 SQLite 事务之外执行清理以更高修订号的墓碑替换活跃 R2 对象。后续 put 可以替换该墓碑但更旧的延迟写入无法重建已清理内容R2 墓碑在 SQLite 中持久排队以有界批次排空。失败的 R2 操作会保留其墓碑排队后续清理可重试而不会丢失防复活围栏anti-resurrection fenceSWR 窗口内的过期 R2 响应会立即返回同时ctx.waitUntil()运行一次带 claim 的再验证后续的 Workers Cache 请求会提升该已完成修订因此可能多出现一次过期响应硬过期响应永远不会被返回读取会等待再验证因此要求存在存储的 revalidator 描述符被放弃的写预留保留一小时之后由 alarm 驱动的清理将它们围栏化防止后续发布。响应体只在 Durable Object 发布后写入 R2因此该清理不执行 R2 操作RPC 传输的响应体会在 R2 写入前缓冲转移的流不保留 R2 单部分 put API 所需的定长标记源码 storeResponse 中的注释明确说明。选择最大响应尺寸时要考虑 Worker 内存限制service-binding 回调始终钉在提供 revalidator 能力时的应用 Worker 版本上。SQLite 侧的表结构entries、entry_tags、revalidation_claims、tag_invalidations、pending_r2_tombstones等在 src/metadata-do.ts 的CacheMetadata构造函数中创建。版本保留与清理backing 数据按应用 Worker 版本 ID隔离新部署会以冷 backing 布局启动同时保留旧版本以支持回滚。部署新版本不会删除旧版本的 R2 对象或元数据 Durable Object且库当前不执行跨版本垃圾回收。这是部署兼容而非原地对象布局迁移单 Worker 模式新版本冷启动旧版本数据保留供回滚service-binding 模式只升级缓存 Worker 时现有应用版本 ID 保持不变但旧 R2 布局中的条目被视为冷未命中现有客户端通过不变 API 重新填充旧缓存键格式仍是合法普通键。旧 R2 对象在该应用版本被清理前一直保留。不要把同一应用版本 ID 手动指派给无关部署。保留每个仍可能接收流量或可回滚到的版本。当某版本永久退役时若它仍可寻址通过该版本调用purge({ purgeEverything: true })墓碑化其元数据与活跃 R2 响应对象这同时会请求宽泛的边缘缓存清理因此其他版本可能需要重新填充删除其 R2 前缀下的所有剩余对象runtime-cache/version-id/包含该应用版本的每个存储布局与分片数量若需回收已退役元数据 Durable Object 的 SQLite 存储通过应用自有的管理路径对每个已知对象调用deleteAll()。当前布局名为version-id:r2-v1无分片或version-id:r2-v1:metadata-shard:index-of-count每个分片一个。更旧布局可能还有额外对象。该清理不属于包 API。注意不要在使用中的版本还共享命名空间时删除 Durable Object namespace除非能保证存活时间超过每个有效响应与回滚窗口否则避免仅按年龄的 R2 生命周期规则——显式的退役版本前缀可以避免误删仍活跃的旧缓存条目。结语cloudflare/workers-response-store提供了一条从边缘缓存到程序化响应存储的完整路径Workers Cache 负责最快的命中路径R2 提供持久化的响应体SQLite Durable Object 提供强一致的元数据、修订、墓碑与标签失效。无论选择单 Worker 的自包含部署还是 service-binding 的独立缓存基础设施统一的fetch/put/refresh/purgeAPI 都能让框架适配层精确控制缓存生命周期同时通过shards与locationHint在扩展性与地域亲和性之间取得平衡。可运行的完整集成示例位于本包的 example/ 目录单 Worker 的 worker.ts 与 service-binding 的 user-worker.ts 及其两份 wrangler 配置对应的端到端测试可参考 tests/e2e.test.ts 与 tests/service-binding-e2e.test.ts它们是理解完整行为契约的最佳入口。赞分享后端Web框架SSR【免费下载链接】vinextVite plugin that reimplements the Next.js API surface — deploy anywhere项目地址https://gitcode.com/gh_mirrors/vi/vinext点击查看免费下载相关推荐深入解析 EmDash blog-cloudflare 模板在 Astro 上构建部署于 Cloudflare Workers 的 CMS 博客深入解析 EmDash blog cloudflare 模板在 Astro 上构建部署于 Cloudflare Workers 的 CMS 博客 导读 temCMS后端前端插件系统Vike 云函数实战在 Cloudflare Workers 上构建 React 流式 SSR 应用examples/cloudflare-workers-react-full 详解Vike 云函数实战在 Cloudflare Workers 上构建 React 流式 SSR 应用examples/cloudflare workers前端后端Web框架SSR在 Cloudflare Workers 上托管 Rivet Actorsrivetkit/cloudflare-workers 实战指南在 Cloudflare Workers 上托管 Rivet Actorsrivetkit/cloudflare workers 实战指南 Rivet Ac后端AI Agent人工智能流程编排WebSocket上一篇如何快速备份微博3步完成完整PDF导出的终极指南下一篇DDrawCompat让经典游戏在现代Windows上完美运行的终极兼容方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考