Cloudflare Workers KV:架构、一致性、API 与选型边界

This article is extracted from the chat log with AI. Please identify it with caution.

AI 参与说明(Agent:Codex /root):本文由 Agent 根据 Cloudflare 官方产品页、开发者文档与公开类型定义辅助整理,并对示例、链接和 Hugo 产物进行校验。资料核验日期为 2026-08-24;价格、限额、Beta 状态和 API 可能继续变化,部署前请复核文末官方来源。

Cloudflare Workers KV 的定位很明确:它是一个面向全球低延迟读取、内置多层缓存、最终一致的持久化 key-value store。最合适的负载是“少写、多读、允许短时间旧值”,例如应用配置、feature flags、路由表、用户偏好和可重建缓存。

它不是通用的 Redis、关系数据库或强一致协调器。余额、库存、座位、计数器、锁、立即生效的权限撤销,以及任何依赖原子 read-modify-write 的状态,不应直接放在 KV 中完成。此类需求通常应选择 Durable Objects 或 D1,再把适合全球分发的读模型投影到 KV。

KV 的架构:中心持久化,按访问位置缓存#

写入 KV 时,数据先进入少量 central data stores,并不会立即推送到 Cloudflare 的每一个 location。某地第一次读取一个 key 时,可能依次经过 local cache、regional tier、central tier,最后访问 central stores;结果会沿读取路径缓存,后续同地的 hot read 才能获得最低延迟。How KV works

flowchart TB
  W["Worker binding / REST API 写入"] --> S[("Central data stores")]
  R["某个 Cloudflare location 发起读取"] --> L{"Local cache 命中?"}
  L -->|是| O["返回 hot value"]
  L -->|否| G{"Regional / central cache 命中?"}
  G -->|是| F["向下层回填缓存"]
  G -->|否| S
  S --> F
  F --> O

这个架构同时解释了 KV 的优势与边界:热点读取可以非常快,冷 key 的第一次读取可能明显更慢;缓存让全球读吞吐容易扩展,也让写入后的读取只能做到 eventual consistency。Cloudflare 产品页给出的 “sub-5 ms hot read” 描述针对 hot read,不应当被理解成每次读取的延迟保证。Workers KV 产品页

一致性:60 秒不是强一致承诺#

KV 最容易被误解的是“写成功”和“全球读到新值”之间的差别:

行为应采用的工程假设
写入发生地的后续读取通常很快可见,但 Cloudflare 明确说明这并不保证;不要依赖 read-your-writes。
其他 location 的读取可能在 60 秒或更久后才看到新值,具体还受既有缓存与 cacheTtl 影响。
读取不存在的 keynegative lookup 也会被缓存;刚创建的 key 仍可能暂时返回 null
同一 key 的并发写入last write wins,竞争写可能互相覆盖;同一 key 最多每秒写 1 次,超出会收到 429
多 key 更新没有跨 key transaction,也没有原子 compare-and-set 或 read-modify-write。

cacheTtl 控制某次读取结果在访问 location 的缓存时长。当前最小值是 30 秒,默认值是 60 秒;调大它可以减少冷读取,却也会延长其他地方看到旧值的窗口。不存在的 key 同样会被缓存。读取 API

不要把 cacheTtlexpirationTtl 混为一谈:

参数所属操作控制什么当前最小值
cacheTtlget() / getWithMetadata()读取结果在访问 location 的缓存时长30 秒
expirationTtlput()key 从写入起还能存活多久60 秒

key 到达 expiration 后会从系统删除;即使读取使用了更长的 cacheTtl,expiration 仍然优先。写入与 expiration

适合与不适合的场景#

需求是否适合 KV原因或替代方案
应用配置、feature flags、A/B test 参数适合读多写少;业务必须接受配置在全球分批生效。
路由映射、redirect rules、慢变 allow-list适合key lookup 简单,hot read 延迟低;撤销窗口必须被显式接受。
用户偏好、公开 JSON、可重建 API cache适合短暂旧值通常可容忍,可用 TTL 管理生命周期。
基本不再修改的版本化对象或小型静态资源适合immutable key 可以避开大部分一致性冲突。
余额、库存、抢购、座位、锁、全局计数器不适合需要原子更新和强一致协调;使用 Durable Objects 或数据库。
高频更新同一个 key不适合同 key 每秒只能写 1 次,且 competing writes 会覆盖。
必须立即生效的 token / 权限撤销不适合单独使用旧授权结果可能继续命中缓存;使用强一致 authority,或明确接受撤销窗口。
SQL、二级索引、聚合和多条件查询不适合list() 只有 prefix、cursor 与字节序排序;使用 D1 或其他数据库。
大文件、媒体、备份不适合单 value 上限 25 MiB;使用 R2。

Cloudflare 官方也把 authentication token lookup 列为示例场景,但这只说明 KV 能快速查 token。由其一致性模型可以推断,它不满足“全球立即撤销”的安全语义。采用 KV 做鉴权读模型时,应把最大陈旧窗口、token 自身过期时间和强一致撤销通道一起纳入设计。

和其他 Cloudflare 数据能力怎么选#

核心需求首选能力
全球读多写少的 key-value 数据,可接受 eventual consistencyWorkers KV
单个业务 key 下的顺序协调、原子状态、WebSocket roomDurable Objects
SQL、关系、索引、事务和报表查询D1
图片、文件、大对象与归档R2
仅缓存 HTTP response,不把缓存当持久数据源Workers Cache API

KV 是一种持久数据服务;Cache API 是面向 HTTP response、按数据中心工作的缓存接口。两者都能加速读取,但生命周期、寻址方式和一致性语义不同,不能互换。Workers 中 binding 与 runtime API 的整体边界可先阅读 Workers:运行时 API、Bindings 与执行模型

具体案例:应用的 AI 模型列表#

如果一个应用要维护数十个 AI 模型的 ID、provider、capabilities、启用状态与排序,默认更适合使用 KV。这类 model catalog 通常由后台或 CI 低频发布,却会在每个请求中高频读取,正好匹配 KV 的“少写、多读、全球缓存”特征。

小型目录可以整体存为一个 JSON value,而不是在请求路径上执行 list()

{
  "schemaVersion": 1,
  "catalogVersion": 42,
  "updatedAt": "2026-08-24T12:00:00Z",
  "models": [
    {
      "id": "example-chat-model",
      "provider": "example-provider",
      "capabilities": ["text", "tools"],
      "enabled": true,
      "order": 10
    }
  ]
}

这种结构只需一次 get("model-catalog:current", "json"),一次 put() 也会替换完整目录,避免读者看到同一个 value 内的半成品。但新旧 value 仍可能因 eventual consistency 在不同 location 并存,因此应由单一发布入口让 catalogVersion 单调递增,读取端也要能接受短暂旧版本。

模型目录要求建议
后台偶尔更新,允许不同地区在一段时间内看到旧列表只用 KV,整体发布一个 versioned JSON document。
多个管理员可能同时编辑,需要版本检查、校验或原子更新用单个 Durable Object 作为 model catalog authority。
模型停用后必须立即阻止新请求请求路径查询 Durable Object 或另一个强一致 authority;不能只信任 KV 的 enabled 字段。
provider 健康状态、实时限流、failover weight 会频繁变化把运行时状态放在 Durable Object 或专用路由层;KV 只保存慢变的目录与默认值。
既需要强一致管理,又需要全球低延迟读取Durable Object 保存事实源,每次成功更新后再向 KV 发布完整快照。
需要按 provider、capability、tenant 做组合查询、审计与历史报表优先用 D1 建模,再视读取流量决定是否发布 KV 读模型。

仅用 Durable Object 读取虽然可以得到强一致结果,但请求必须路由到该唯一 object,不再具有 KV 的全球 hot-read 特征。混合方案中,Durable Object 先完成校验与持久化,再发布例如 model-catalog:v42 的 immutable snapshot 和 model-catalog:current manifest。manifest 仍然是 eventual consistency,所以这是“强一致管理面 + 最终一致读取面”,不是让 KV 变成强一致。Use Workers KV from Durable Objects

最小可运行示例:用 KV 分发 feature flag#

下面采用“单一管理入口写入、全球 Worker 只读”的模式。它比在所有请求中同时读写同一个 key 更贴合 KV 的设计。

1. 创建 namespace 与 binding#

在已有 Worker 项目中执行:

npx wrangler kv namespace create FEATURE_FLAGS

命令会创建远程 namespace 并输出 ID。把 ID 写入 wrangler.jsonc

{
  "$schema": "./node_modules/wrangler/config-schema.json",
  "name": "kv-feature-flags",
  "main": "src/index.ts",
  "compatibility_date": "2026-08-24",
  "kv_namespaces": [
    {
      "binding": "FEATURE_FLAGS",
      "id": "<YOUR_KV_NAMESPACE_ID>"
    }
  ]
}

binding 是 Worker 代码中的变量名,namespace ID 才指向实际资源。staging 与 production 应使用不同 namespace,但可以保留同一个 binding name;Cloudflare 支持在 Wrangler environments 中分别声明 ID。Getting started Environments

配置完成后运行 npx wrangler types,让生成的 Env 类型与实际 binding 保持一致。为便于单文件阅读,下面仍显式写出 Env

type FeatureFlag = {
  enabled: boolean;
  rollout: number;
};

type FlagMetadata = {
  schemaVersion: 1;
  updatedAt: string;
};

interface Env {
  FEATURE_FLAGS: KVNamespace;
}

export default {
  async fetch(request, env): Promise<Response> {
    const url = new URL(request.url);
    const match = /^\/flags\/([a-z0-9-]+)$/.exec(url.pathname);

    if (request.method !== "GET" || match === null) {
      return new Response("Not Found", { status: 404 });
    }

    const name = match[1];
    const result = await env.FEATURE_FLAGS.getWithMetadata<
      FeatureFlag,
      FlagMetadata
    >(`flag:${name}`, {
      type: "json",
      cacheTtl: 60,
    });

    if (result.value === null) {
      return Response.json({ error: "flag not found" }, { status: 404 });
    }

    return Response.json(
      {
        name,
        value: result.value,
        metadata: result.metadata,
      },
      {
        headers: {
          "Cache-Control": "no-store",
        },
      },
    );
  },
} satisfies ExportedHandler<Env>;

2. 写入本地测试数据并验证#

wrangler dev 默认使用隔离的本地 KV,不会读取或修改远程 namespace。先写入本地数据:

npx wrangler kv key put \
  --binding=FEATURE_FLAGS \
  "flag:checkout" \
  '{"enabled":true,"rollout":25}' \
  --metadata='{"schemaVersion":1,"updatedAt":"2026-08-24T00:00:00Z"}' \
  --local

npx wrangler dev

另开终端请求:

curl http://localhost:8787/flags/checkout

预期得到等价 JSON:

{
  "name": "checkout",
  "value": {
    "enabled": true,
    "rollout": 25
  },
  "metadata": {
    "schemaVersion": 1,
    "updatedAt": "2026-08-24T00:00:00Z"
  }
}

需要明确操作远程 namespace 时才添加 --remote,或在 binding 上配置 "remote": true。远程读写会计入用量,也可能影响线上数据;不要为了让本地示例“读得到”而默认连接 production。Local development 行为

Workers Binding API 地图#

操作常用接口关键语义
读单个 keyget(key, type/options)缺失时返回 null;可读成 textjsonarrayBufferstream
读多个 keyget(keys, type/options)最多 100 个 key,只支持 text / json;总 response 上限 25 MB。
连 metadata 读取getWithMetadata()返回 { value, metadata };同样可能读到旧值。
写入put(key, value, options)可设置 expirationexpirationTtl 与最多 1024 bytes 的 JSON metadata。
删除delete(key)删除不存在的 key 也成功;其他 location 仍可能暂时读到旧值。
列举 keylist({ prefix, limit, cursor })每页最多 1,000 个,按 UTF-8 bytes 字典序返回;用 list_complete 判断是否结束。

单次 Worker invocation 最多进行 1,000 次 external service operations,这一额度由 KV、R2 等外部服务调用共享。一次 bulk read 在这个调用额度中算 1 次,但计费仍按读取的 key 数量计算。Workers binding 不提供 bulk write;Wrangler 或 REST API 的 bulk write 一次最多 10,000 个 key-value pairs,整个 request 必须小于 100 MB。读取 API 写入 API List API

数据建模与生产模式#

1. 先按隔离边界拆 namespace#

namespace 应表达真正的运维与隔离边界,而不是模拟关系数据库的每张表。可以把拆分公式概括为:

namespace = environment × trust/owner × lifecycle × jurisdiction
key prefix = tenant × domain × entity × version
边界是否应拆 namespace原因
production、staging、development必须拆防止测试、回填和清理脚本触及线上数据。
不同 Worker 的信任或所有权边界通常应拆只给需要数据的 Worker 注入 binding;key prefix 不是授权边界。
长期配置与可丢弃 runtime cache按需拆当负责人、批量清理、迁移、发布路径或告警策略独立时再拆;TTL 是 per-key 能力,不构成单独拆分理由。
不同法规数据位置必须拆Jurisdiction 只能在 namespace 创建时设定,之后不能添加或修改。
provider、模型、功能或版本通常不拆使用 key prefix 和 versioned key 即可。
大量 tenant,访问策略与生命周期相同默认不拆使用 tenant:<id>: prefix;必须在 Worker 中做授权校验。

Cloudflare 当前限制每个 account 最多 1,000 个 namespaces,但每个 namespace 的 key 数量是 Unlimited。因此“每个 tenant 或每个模型一个 namespace”会很快形成 namespace explosion,也会让 binding、部署和监控难以管理。namespace ID 是 public identifier,不是 credential;真正的授权仍由 Worker 逻辑、binding 暴露范围与 API Token 权限完成。KV limits KV namespaces

对上文的 model catalog,推荐的 namespace title 命名规则是 <app>-<environment>-<data-domain>[-<jurisdiction>]

model-router-development-model-catalog
model-router-staging-model-catalog
model-router-production-model-catalog
model-router-development-runtime-cache
model-router-staging-runtime-cache
model-router-production-runtime-cache

title 主要服务于 Dashboard、CLI 和运维识别;代码中的 binding 应使用稳定的语义名,例如 MODEL_CATALOGRUNTIME_CACHE,不在 binding 中加 PROD_STAGING_。这样同一份 Worker 代码在各环境中都使用 env.MODEL_CATALOG,只由 Wrangler 配置切换 namespace ID。KV environments

可先创建三个独立 namespace:

npx wrangler kv namespace create model-router-development-model-catalog
npx wrangler kv namespace create model-router-staging-model-catalog
npx wrangler kv namespace create model-router-production-model-catalog

然后把返回的 ID 显式写入各 environment:

{
  "$schema": "./node_modules/wrangler/config-schema.json",
  "name": "model-router",
  "main": "src/index.ts",
  "compatibility_date": "2026-08-24",
  "kv_namespaces": [
    {
      "binding": "MODEL_CATALOG",
      "id": "<DEVELOPMENT_NAMESPACE_ID>"
    }
  ],
  "env": {
    "staging": {
      "kv_namespaces": [
        {
          "binding": "MODEL_CATALOG",
          "id": "<STAGING_NAMESPACE_ID>"
        }
      ]
    },
    "production": {
      "kv_namespaces": [
        {
          "binding": "MODEL_CATALOG",
          "id": "<PRODUCTION_NAMESPACE_ID>"
        }
      ]
    }
  }
}

kv_namespaces 是 Wrangler environment 的 non-inheritable 配置;一个 environment 需要多个 KV binding 时,要在该 environment 中完整重复所有条目,不能假设它会从顶层继承。wrangler dev 默认使用本地 KV,即使配置中有 namespace ID 也不会直接读写远程数据。只在集成测试确实需要时,才对专用 development namespace 设置 "remote": true;不要把本地 Worker 指向 production ID。KV bindings Wrangler environments

同理,--env=staging 只负责选择 staging 配置,不表示“使用远程数据”。当 wrangler kv key put/get/list/delete 的目标是 Cloudflare 上的真实 namespace 时,应显式传入 --remote,例如 npx wrangler kv key put --env=staging --binding=MODEL_CATALOG --remote "catalog:current" "<JSON>"。这个显式标志能避免操作者把 local、staging 和 production 的对象混为一谈。Wrangler KV commands

普通 multi-tenant 应用应先用稳定、不可变的 tenant ID 组成 key prefix。只有在 tenant 数量明确受控,且确实需要独立授权、整体删除、Jurisdiction 或故障域时,才考虑 namespace-per-tenant;即便如此,namespace title 和 ID 也不能替代 Worker 的 tenant 授权检查。

Workers KV Jurisdictions 截至核验日仍是 private beta。它只约束 durable storage location,数据仍可能缓存在 jurisdiction 之外;因此在 namespace title 中加 euusfedramp 只是运维约定,必须与创建时的实际 jurisdiction 参数和完整合规方案一起验证。Data location

2. 让 key 表达逻辑命名空间与版本#

KV 没有表和二级索引,通常用可预测的 prefix 组织 key:

config:checkout:v3
config:search:v8
tenant:acme:preferences
cache:catalog:2026-08-24

把基本不变的数据写到 versioned key,再最后更新一个 manifest,可以减少多 key 发布时“新旧字段混在一起”的概率。不过 manifest 自身仍是 eventual consistency,不能把这种模式称为 transaction。

3. 单写者,多读者#

对同一个逻辑 key,优先让 CI、管理 API 或对应的 Durable Object 成为唯一写入路径。这样可以避免 competing writes;若同 key 写入仍可能撞上每秒 1 次限制,应合并更新并对 429 做带抖动的 exponential backoff。不要在每个全球请求中执行“先读、修改、再写”的非原子流程。

写入成功后,响应应直接返回本次已知的新值,不要马上重新读取 KV 来“确认”写入;重新读取可能命中旧值或 negative cache。

4. 合并相关小值,但保留更新边界#

把总是一起读取的多个小字段合并成一个 JSON value,可以减少操作数并让冷字段随热点字段一起缓存。代价是更新其中一个字段也要重写整个对象,并引入 read-modify-write 冲突。需要独立、高频更新的字段不应为了少一次读取而强行合并。

5. 不把 list() 当查询引擎#

list() 适合管理工具、prefix 扫描和离线任务。它没有任意条件过滤、聚合或 secondary index,而且每次 list 都计费。线上请求若依赖完整扫描,应改为维护明确索引、使用 D1,或重新设计访问模式。

当前限额与价格#

以下数据按 2026-08-24 的官方页面整理。Cloudflare 的 Limits 页面中,Paid 的 “Unlimited” 表示没有 Free plan 那样的每日操作/存储配额,并不表示超出套餐包含量后免费。

固定技术限额#

项目Free / Paid
同一 key 写入每秒 1 次
每次 Worker invocation 的 external service operations1,000 次
每账号 namespaces1,000 个
key 长度512 bytes
value 大小25 MiB
metadata 大小1,024 bytes
单 namespace 的 key 数量Unlimited
最小 cacheTtl30 秒

Free plan 的 storage/account 和 storage/namespace 均为 1 GB;Paid plan 当前列为 Unlimited。需要更高的可调整限额时,可通过官方 limit increase form 申请。KV limits

套餐包含量与超额价格#

项目Free planPaid plan 包含量与超额价格
读取100,000 keys / day10 million / month,之后 $0.50 / million
写入1,000 keys / day1 million / month,之后 $5.00 / million
删除1,000 keys / day1 million / month,之后 $5.00 / million
List requests1,000 / day1 million / month,之后 $5.00 / million
存储1 GB1 GB,之后 $0.50 / GB-month

Free plan 配额在每天 00:00 UTC 重置,某一类操作超额后,该类后续操作会失败。读取不存在的 key 也算一次读取;Dashboard 与 Wrangler 操作同样计费;bulk read 按 key 数量计费。KV 当前不收 data transfer / egress 费,但 Worker compute 与其他产品费用需要另算。KV pricing

安全、数据位置与可观测性#

  • Cloudflare 表示 KV value 会自动使用 AES-256 加密 at rest,当前优先采用 GCM;Worker、Cloudflare 内部节点、HTTP API 与 Wrangler 的传输使用 TLS。这里的 encryption key 由 Cloudflare 管理;需要应用层密钥控制时,应在写入前自行加密。Data security
  • Worker 内优先使用 binding,让资源访问能力随部署配置注入;外部自动化使用最小权限 API Token,不要在代码或仓库中放 Global API key。部署本身的 secret 应使用 Workers Secrets 或 Secrets Store,而不是普通 KV value。
  • 默认情况下,数据会全球复制。Workers KV Jurisdictions 截至核验日仍是 private beta;即使限制了 namespace 的 durable storage location,KV data 仍可能缓存在 jurisdiction 之外。合规设计不能只看 namespace 创建参数。Data location
  • Dashboard 和 GraphQL Analytics API 提供 per-namespace operations、storage 与 read latency 数据,当前保留 31 天。生产告警还应覆盖 429、读写错误、冷读取长尾、Free plan 配额耗尽,以及应用侧 fallback 是否持续触发;应用层 missing-key rate 需要自行埋点。Metrics and analytics

上线前检查清单#

  • 业务已定义可接受的陈旧阈值,并在超过阈值时具备 fallback 或强一致查询路径;没有把“通常 60 秒”当作硬性 SLA 上界。
  • 余额、库存、权限撤销、锁和计数器等强一致状态不以 KV 为唯一事实来源。
  • staging、production 使用不同 namespace;本地默认使用 local KV。
  • 同一 key 有单写者,高频数据已拆 key、聚合或迁移到 Durable Objects。
  • cacheTtl、key expiration 与业务 TTL 分别设计,没有混用。
  • key 命名、JSON schema、metadata version、空值与 null fallback 均有约定。
  • 已估算 missing-key reads、Dashboard / Wrangler 操作与 bulk per-key billing。
  • 已监控 operation、storage、latency、429 和配额失败,并准备降级路径。

小结#

Workers KV 的核心价值不是“又一个 key-value API”,而是把持久数据按访问热度带到 Cloudflare 的全球缓存层。只要业务能接受 eventual consistency,并把写入集中、key 版本化、陈旧窗口与失败降级设计清楚,它非常适合配置与读模型分发;一旦需求变成原子更新、高频同 key 写入或立即撤销,就应把强一致事实来源放在 Durable Objects、D1 或其他数据库中,再决定是否把只读投影同步到 KV。

官方来源#

本文共 7085 字,创建于 Aug 24, 2026

相关标签: Cloud, Database, TypeScript, ByAI