Chapter 05

KV 与 Cache API

KV 是全球最终一致键值库,适合"写得少、读得飞快"。Cache API 是 HTTP 语义的缓存,适合"fetch 结果想缓存"。两者加 D1 三选一,是 Cloudflare 的数据层心智模型。

KV 的关键特性

全球读,< 10ms
每个 PoP 有读缓存,热键基本是本地读。
写最终一致
写进 KV 后,全球 PoP 最多需要 60 秒看到——是的,60 秒。这个特性决定了什么可以放 KV。
单值最大 25MB
够放大多数配置、HTML 片段、JWK。真正大文件去 R2。
TTL 支持
expirationTtl 秒数,或 expiration UNIX 时间戳。

创建 KV Namespace

wrangler kv namespace create "CONFIG"
# 输出:id = "abc123..."

# wrangler.toml:
# [[kv_namespaces]]
# binding = "CONFIG"
# id = "abc123..."

读写 API

type Bindings = { CONFIG: KVNamespace };

app.get('/feature/:flag', async (c) => {
  const val = await c.env.CONFIG.get(c.req.param('flag'));
  return c.text(val ?? 'off');
});

app.post('/feature/:flag', async (c) => {
  const value = await c.req.text();
  await c.env.CONFIG.put(c.req.param('flag'), value, {
    expirationTtl: 3600,  // 1 小时后自动删
    metadata: { updatedBy: 'admin' },
  });
  return c.text('ok');
});

// 读多种类型
const json = await c.env.CONFIG.get('user:42', { type: 'json' });
const bytes = await c.env.CONFIG.get('image:42', { type: 'arrayBuffer' });

// list by prefix
const r = await c.env.CONFIG.list({ prefix: 'user:', limit: 100 });

KV 适合与不适合

适合不适合
Feature flags(改完 60 秒传播可接受)用户刚改的资料立即显示(违反写一致)
路由表 / 配置 / A/B 分桶余额、库存(读到旧值灾难)
会话 token 验证(可短 TTL)计数器 / 速率限制(用 DO)
用户偏好(容忍滞后)高频写(每键每秒 1 次写限制)
同一个键的写频率限制
KV 同一个 key 每秒最多写 1 次。用 KV 做计数器会很快报错。计数场景用 Durable Object + SQLite storage。

Cache API:HTTP 语义缓存

Workers 里的 caches.default 是一个 per-PoP 的 HTTP 缓存——存的是 Response,key 是 Request。它不是 KV,两者逻辑不同。

app.get('/api/weather/:city', async (c) => {
  const cache = caches.default;

  // 查缓存
  let res = await cache.match(c.req.raw);
  if (res) {
    console.log('cache hit');
    return res;
  }

  // 回源
  res = await fetch(`https://api.weather.com/${c.req.param('city')}`);
  res = new Response(res.body, res);
  res.headers.set('cache-control', 'public, max-age=300');

  // 异步写缓存,不等
  c.executionCtx().waitUntil(cache.put(c.req.raw, res.clone()));
  return res;
});

关键 API

caches.default
默认 cache,所有 Worker 共享同一 PoP 的缓存层。
caches.open(name)
自定义命名 cache,独立 namespace(付费版功能)。
waitUntil(cache.put(...))
不阻塞返回响应。如果同步 await,用户要等缓存写完才拿到数据。

KV vs Cache API vs D1 选型

需要存"键 -> 值"? │ ├─ 值是 HTTP Response 或从 fetch() 来的? │ ├─ 是 → Cache API(快、自动按 URL 失效) │ └─ 否 → ↓ │ ├─ 需要全球一致,或强读自己写? │ ├─ 是 → D1(主库可写)或 Durable Object(强一致) │ └─ 否 → ↓ │ ├─ 高频写(> 1/sec/key)? │ ├─ 是 → Durable Object │ └─ 否 → KV(最简单最便宜)

cache 失效策略

Cache API 本身没 Purge API。想让缓存提前失效:

  1. 在 URL 加版本号(?v=123),版本变 → 新 key,老缓存自然过期
  2. Cache-Control: s-maxage 短一点(1-10 秒),容忍一点滞后
  3. Cloudflare Dashboard 的"Purge Cache"按钮(整站或按 URL,面向 CDN 层而不是 Worker cache)

tiered cache:把 KV 和 Cache 串起来

热键用 Cache(同 PoP 最快),Cache miss 时再读 KV(全球但较慢),形成两层缓存:

async function getConfig(c, key: string) {
  const cacheKey = new Request(`https://cache.local/config/${key}`);
  let res = await caches.default.match(cacheKey);
  if (res) return await res.json();

  const fromKV = await c.env.CONFIG.get(key, { type: 'json' });
  if (fromKV) {
    res = new Response(JSON.stringify(fromKV), {
      headers: { 'cache-control': 'public, max-age=60' },
    });
    c.executionCtx().waitUntil(caches.default.put(cacheKey, res.clone()));
  }
  return fromKV;
}

60 秒内大多命中 cache(快、0 KV 读),60 秒后命中 KV(一次读,刷 cache)。

成本与配额

资源FreePaid
KV 存储1GB无上限,$0.5/GB·月
KV 读100K/天$0.5/百万
KV 写1K/天$5/百万
Cache API不计费(面向 Worker)

本章小结