切换语言
切换主题

Cloudflare Workers KV 实战:分布式键值存储从入门到精通

凌晨三点,我盯着 Cloudflare Dashboard 上的延迟曲线。那条红线还在 200ms 以上徘徊——明明用了 Workers,明明代码已经够精简了,为什么每个用户请求还是要等这么久?

问题出在数据库。每个 session 查询都要从边缘节点跑到欧洲的数据中心,再跑回来。这一来一回,就算 Workers 执行只需要 5ms,网络传输也把时间吃光了。

后来我才明白,Workers 本身是无状态的。你需要一种真正”住在边缘”的存储——Cloudflare Workers KV。

这篇文章,我想把自己踩过的坑、测过的数据、写过的代码都摊开来讲。从 KV 到底是什么、为什么能做到 sub-10ms 延迟,到 session storage 和 API cache 的完整实现。对了,还会聊聊什么时候该用 KV、什么时候 D1 或 R2 更合适——这个选择其实挺关键的。

KV 是什么 — 理解分布式 Edge 存储

说白了,KV 就是 Cloudflare 给 Workers 配的一块”随身内存”。这块内存不是放在某个固定机房里,而是跟着全球 300+ 个边缘节点散开分布。你在东京的用户请求,数据可能就在东京的边缘节点等着;你在法兰克福的请求,数据可能已经在法兰克福的缓存里了。

Cloudflare Workers KV 是专为边缘计算设计的全球分布式键值存储。它最核心的特点有三个:

读取超快。hot keys 的缓存命中延迟在 500µs 到 10ms 之间——说实话,第一次看到这个数据我有点怀疑,直到自己跑了一组 benchmark,确实能稳定在个位数毫秒级别。

全球复制。你写一条数据,它会被复制到全球所有边缘节点。这一点跟 Redis 集群不太一样,KV 的数据模型是”写一次,到处可读”,适合读多写少的场景。

高吞吐。单个 key 的读取可以达到数千 RPS(requests per second),因为数据在边缘缓存,不用每次都回源。

500µs - 10ms
Hot Keys 延迟范围

Cloudflare 存储全家桶对比

KV 只是 Cloudflare 存储矩阵的一块拼图。先看看完整图景:

存储服务数据模型最适合的场景写入限制延迟特点
KVKey-ValueSession、Cache、配置1 RPS/keyhot keys 500µs-10ms
D1SQL(SQLite)用户数据、订单、报表无硬限制取决于位置,通常 50-200ms
R2Object Storage文件、图片、视频无硬限制下载快,上传看文件大小
Durable Objects有状态对象协作编辑、WebSocket无硬限制需要定位到特定节点

看到这张表,你可能会有疑问:1 RPS/key 是什么意思?这个后面会详细讲,简单说就是每个 key 每秒只能写一次,是 KV 最需要注意的限制。

KV 适用场景速查

什么时候应该考虑 KV?给你一个简单的判断标准:

推荐用 KV

  • Session storage(用户登录状态)
  • API response cache(第三方 API 返回值)
  • Rate limiting counters(频率限制计数)
  • Feature flags / 配置数据
  • Redirect mapping(URL 重定向规则)

不推荐用 KV

  • 需要频繁写入的数据(比如实时计数器,超过 1 RPS 就不行)
  • 需要 SQL 查询的复杂数据(用户表、订单表用 D1)
  • 大文件存储(图片、视频用 R2)
  • 需要强一致性的金融交易数据(用 Durable Objects)

Cloudflare 官方文档里写得挺清楚:KV 适合”高读取率、低修改频率、不需要即时一致性”的场景。OpenAuth 等认证框架也把 KV 作为默认的 session storage——这个后面我会给一个完整的实现代码。

KV 架构深度解析 — 为什么这么快

KV 的速度不是魔法,而是三层缓存架构的结果。

想象你在一家便利店买东西。最理想的情况是货就在柜台旁边的货架上,伸手就能拿(edge cache);稍差一点,货在仓库里,店员跑去拿(regional cache);最慢的情况,货在总仓库,要等卡车送过来(central store)。

KV 的架构就是这三层:

请求 → Edge Cache (最快)
        ↓ 未命中
      Regional Cache
        ↓ 未命中
      Central Store (最慢)

据 Cloudflare 2025 年 10 月的博客数据,大约 30% 的请求能在缓存层直接解决。这意味着三分之一的读取根本不用跑回中央存储,延迟自然就下来了。

30%
边缘缓存命中率

性能数据:从官方到实测

Cloudflare 官方文档给了一组参考数据:

  • Hot keys(频繁访问的 key):500µs 到 10ms
  • Cold keys(首次访问或很少访问):延迟更高,需要回源

说实话,“500µs”这个数字我一开始不太信。后来自己测了一下:

// 简单的延迟测试代码
const start = Date.now();
await env.KV.get("test-key");
const latency = Date.now() - start;
console.log(`Latency: ${latency}ms`);

测了 100 次,hot keys 的平均延迟确实在 5-8ms 左右。cold keys 第一次访问会到 50ms 以上,但第二次访问就降下来了——缓存生效了。

2025 年 Cloudflare 对 KV 做了一次大改造,据官方博客数据,操作速度提升了 3 倍。主要是两个改动:

  1. Workers 和 KV 直接连接,绕过了原来的 Front Line 层
  2. 简化了内部数据传输路径

这个改动对依赖 KV 的其他 Cloudflare 服务(比如 Turnstile、Waiting Room)也产生了级联收益。

一致性模型:最终一致性的代价

KV 是最终一致性(eventually consistent)的存储。什么意思?

你写一条数据,它不会立刻出现在所有边缘节点。传播需要时间——官方没有给精确数字,但实际测试中,跨区域传播通常在几秒到几十秒之间。

这个特性在某些场景是问题,在另一些场景完全不重要:

问题场景

  • 用户刚登录,session 写入 KV,但另一个请求打到另一个边缘节点,读不到 session——登录”失败”
  • 实时协作编辑,A 用户修改,B 用户立刻读,看不到最新内容

不问题场景

  • Feature flags 配置,改了之后等几秒生效,完全 OK
  • API cache,第三方 API 返回值缓存几分钟,延迟传播无所谓
  • Redirect mapping,URL 规则更新慢几秒,用户几乎感知不到

如果你的场景需要即时一致性,KV 可能不适合。这种情况下,Durable Objects 会是更好的选择——它会定位到特定节点,保证状态一致性。

Wrangler CLI 实战配置

理论讲完了,开始动手。

KV 的配置分两部分:创建 namespace(命名空间),然后在 wrangler.toml 里绑定到你的 Worker。

创建 Namespace

Namespace 是 KV 的”容器”,每个 namespace 可以存无数个 key-value pair,但账户总共只能有 1000 个 namespace(这个限制 2025 年初从 200 提升到 1000)。

# 创建 production namespace
wrangler kv namespace create MY_KV

# 输出类似这样:
# Created namespace with id "abc123def456..."
# Add the following to your wrangler.toml:
# [[kv_namespaces]]
# binding = "MY_KV"
# id = "abc123def456..."

对了,你还需要一个 preview namespace,用于本地开发测试:

# 创建 preview namespace
wrangler kv namespace create MY_KV --preview

# 输出类似:
# Created preview namespace with id "preview_abc123..."

wrangler.toml 配置详解

把上面输出的 id 填到 wrangler.toml 里:

name = "my-worker"
main = "src/index.ts"

[[kv_namespaces]]
binding = "MY_KV"
id = "abc123def456..."        # production namespace
preview_id = "preview_abc123..." # preview namespace(本地开发用)

binding 这个名字很重要。它决定了你在 Worker 代码里怎么访问 KV:

// binding = "MY_KV",代码里就是 env.MY_KV
const value = await env.MY_KV.get("some-key");

REST API vs Workers Binding API

有两种方式访问 KV 数据:

Workers Binding API(推荐):

  • 在 Worker 里直接用 env.MY_KV.get()
  • 不需要额外的网络请求,速度最快
  • 完全免费(只计入 Worker 执行时间)

REST API

  • 用 HTTP 请求访问 KV
  • 需要认证 token,适合外部系统调用
  • 受 Cloudflare REST API 总体速率限制

说实话,绝大多数场景都应该用 Binding API。REST API 主要用于:

  • 外部系统需要读写 KV 数据
  • CI/CD 流程中批量导入数据
  • 临时调试和运维操作

常用 Wrangler KV 命令

Wrangler 提供了一组命令行工具,方便操作 KV 数据:

# 写入数据
wrangler kv key put --namespace-id=abc123 "my-key" "my-value"

# 读取数据
wrangler kv key get --namespace-id=abc123 "my-key"

# 删除数据
wrangler kv key delete --namespace-id=abc123 "my-key"

# 列出所有 key(支持前缀过滤)
wrangler kv key list --namespace-id=abc123 --prefix="session:"

这些命令在调试时挺有用的,但生产环境还是用 Worker 代码操作更高效。

TypeScript 代码实战

终于到了代码部分。这部分我会给你完整的、可运行的示例。

基础 CRUD 操作

先看最基础的增删改查:

// src/index.ts
interface Env {
  MY_KV: KVNamespace;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const path = url.pathname;

    // 写入数据
    if (path === "/put") {
      const key = url.searchParams.get("key") || "default";
      const value = url.searchParams.get("value") || "hello";
      
      await env.MY_KV.put(key, value);
      return new Response(`Saved: ${key} = ${value}`);
    }

    // 读取数据
    if (path === "/get") {
      const key = url.searchParams.get("key") || "default";
      const value = await env.MY_KV.get(key);
      
      if (value === null) {
        return new Response("Key not found", { status: 404 });
      }
      return new Response(value);
    }

    // 删除数据
    if (path === "/delete") {
      const key = url.searchParams.get("key") || "default";
      await env.MY_KV.delete(key);
      return new Response(`Deleted: ${key}`);
    }

    // 列出 keys(带前缀)
    if (path === "/list") {
      const prefix = url.searchParams.get("prefix") || "";
      const keys = await env.MY_KV.list({ prefix });
      
      const keyList = keys.keys.map(k => k.name).join("\n");
      return new Response(keyList || "No keys found");
    }

    return new Response("Try /put, /get, /delete, or /list");
  },
};

这段代码可以直接用 Wrangler 跑起来:

wrangler dev
# 测试写入
curl "http://localhost:8787/put?key=test&value=helloworld"
# 测试读取
curl "http://localhost:8787/get?key=test"

Session Storage 完整实现

这是 KV 最常见的应用场景之一。下面是一个完整的 session 管理代码:

// src/session.ts
interface SessionData {
  userId: string;
  email: string;
  createdAt: number;
  expiresAt: number;
}

interface Env {
  SESSION_KV: KVNamespace;
}

const SESSION_TTL = 3600; // 1 小时过期

class SessionManager {
  private kv: KVNamespace;

  constructor(kv: KVNamespace) {
    this.kv = kv;
  }

  // 创建 session
  async create(userId: string, email: string): Promise<string> {
    const sessionId = crypto.randomUUID();
    const sessionData: SessionData = {
      userId,
      email,
      createdAt: Date.now(),
      expiresAt: Date.now() + SESSION_TTL * 1000,
    };

    // 写入 KV,设置 TTL(自动过期)
    await this.kv.put(
      `session:${sessionId}`,
      JSON.stringify(sessionData),
      { expirationTtl: SESSION_TTL }
    );

    return sessionId;
  }

  // 读取 session
  async get(sessionId: string): Promise<SessionData | null> {
    const raw = await this.kv.get(`session:${sessionId}`);
    if (!raw) return null;

    try {
      return JSON.parse(raw) as SessionData;
    } catch {
      return null;
    }
  }

  // 删除 session(登出)
  async delete(sessionId: string): Promise<void> {
    await this.kv.delete(`session:${sessionId}`);
  }

  // 刷新 session(延长过期时间)
  async refresh(sessionId: string): Promise<boolean> {
    const session = await this.get(sessionId);
    if (!session) return false;

    session.expiresAt = Date.now() + SESSION_TTL * 1000;
    await this.kv.put(
      `session:${sessionId}`,
      JSON.stringify(session),
      { expirationTtl: SESSION_TTL }
    );

    return true;
  }
}

// Worker 入口
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const sessionManager = new SessionManager(env.SESSION_KV);
    const url = new URL(request.url);

    // 登录(创建 session)
    if (url.pathname === "/login" && request.method === "POST") {
      const body = await request.json();
      const sessionId = await sessionManager.create(
        body.userId as string,
        body.email as string
      );
      
      return new Response(JSON.stringify({ sessionId }), {
        headers: { "Content-Type": "application/json" },
      });
    }

    // 验证 session
    if (url.pathname === "/verify") {
      const sessionId = url.searchParams.get("sessionId");
      if (!sessionId) {
        return new Response("Missing sessionId", { status: 400 });
      }

      const session = await sessionManager.get(sessionId);
      if (!session) {
        return new Response("Session not found", { status: 401 });
      }

      return new Response(JSON.stringify(session), {
        headers: { "Content-Type": "application/json" },
      });
    }

    // 登出
    if (url.pathname === "/logout") {
      const sessionId = url.searchParams.get("sessionId");
      if (sessionId) {
        await sessionManager.delete(sessionId);
      }
      return new Response("Logged out");
    }

    return new Response("Not found", { status: 404 });
  },
};

几个关键点:

  1. TTL 自动过期expirationTtl 参数让 KV 自动删除过期数据,不用手动清理
  2. key 前缀:用 session: 作为前缀,方便批量查询和区分不同类型的数据
  3. JSON 序列化:KV 只存字符串,复杂对象需要手动 JSON.stringify/parse

API Response Cache 实现

另一个常见场景:缓存第三方 API 的返回值,减少调用次数和延迟。

// src/api-cache.ts
interface Env {
  CACHE_KV: KVNamespace;
}

const DEFAULT_CACHE_TTL = 300; // 5 分钟缓存

async function cachedFetch(
  kv: KVNamespace,
  cacheKey: string,
  url: string,
  ttl: number = DEFAULT_CACHE_TTL
): Promise<Response> {
  // 先尝试从缓存读取
  const cached = await kv.get(cacheKey, "text");
  
  if (cached) {
    console.log(`Cache hit: ${cacheKey}`);
    return new Response(cached, {
      headers: {
        "Content-Type": "application/json",
        "X-Cache": "HIT",
      },
    });
  }

  // 缓存未命中,调用真实 API
  console.log(`Cache miss: ${cacheKey}`);
  const response = await fetch(url);
  const body = await response.text();

  // 写入缓存(使用 cacheTtl 优化读取性能)
  await kv.put(cacheKey, body, {
    expirationTtl: ttl,
    // cacheTtl: 让边缘缓存更久,减少回源
  });

  return new Response(body, {
    headers: {
      "Content-Type": "application/json",
      "X-Cache": "MISS",
    },
  });
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const apiUrl = url.searchParams.get("api");

    if (!apiUrl) {
      return new Response("Missing api parameter", { status: 400 });
    }

    // 用 API URL 作为缓存 key
    const cacheKey = `api:${apiUrl}`;
    
    return cachedFetch(env.CACHE_KV, cacheKey, apiUrl);
  },
};

cacheTtl 参数优化

这是 KV 性能优化中最容易被忽略的参数。

cacheTtl 控制边缘缓存的存活时间。默认值是 60 秒,意味着 60 秒内重复读取同一个 key,可以直接从边缘缓存拿,不用回源。

对于热点数据,可以把 cacheTtl 设置更高:

// 高频访问的配置数据,设置更长的边缘缓存
await env.MY_KV.get("config:feature-flags", {
  cacheTtl: 3600, // 1 小时边缘缓存
});

这样,即使 KV 的 central store 数据没变,边缘节点也会缓存 1 小时。对于 feature flags 这种”改了也不急着生效”的场景,超有用。

KV vs D1 vs R2 — 存储选择决策指南

这部分说实话,我一开始也纠结过。Cloudflare 给了这么多存储选项,到底选哪个?

给你一个决策树:

场景匹配决策树

你的数据是什么类型?

├─ 需要文件存储(图片、视频、PDF)?
│   └─ YES → R2

├─ 需要 SQL 查询(用户表、订单、多表关联)?
│   └─ YES → D1

├─ 简单的 key-value,读多写少?
│   ├─ 写入频率 &gt; 1 RPS/key?
│   │   └─ YES → 不适合 KV,考虑 D1 或 Durable Objects
│   │
│   └─ NO → KV ✓

├─ 需要即时一致性?
│   └─ YES → Durable Objects
│   └─ NO → KV 可能 OK

└─ 不知道?
    └─ 先试 KV,够用就别换
500µs-10ms
KV Hot Key 延迟
50-200ms
D1 延迟
25MB
KV 最大 Value
数据来源: Cloudflare 官方文档

详细对比表格

对比维度KVD1R2
数据模型Key-ValueSQL(SQLite)Object Storage
查询能力只有 get/put/delete完整 SQL 查询无查询,只有路径
写入限制1 RPS per key无硬限制无硬限制
读取延迟500µs - 10ms(hot)50-200ms(取决于位置)快(下载)
一致性最终一致强一致(单区域)最终一致
最大 value25 MBSQLite 行限制5 TB 单文件
免费额度100k reads/day5 GB 存储 + 25M rows read10 GB 存储
典型场景Session、Cache、Config用户数据、订单、报表文件、图片、备份

具体场景推荐

用户认证 / Session
KV

理由:session 数据是简单的 key-value,读取频率高(每次请求都要验证),写入频率低(只在登录/登出时)。OpenAuth 等认证框架默认就用 KV。

// session:userId → session data
await env.SESSION_KV.put(`session:${sessionId}`, JSON.stringify(session));

用户资料 / 订单管理
D1

理由:需要 SQL 查询(“查找某用户的所有订单”、“统计上月销售额”),KV 的 get/put 模式做不了这种查询。

-- D1 可以做复杂查询
SELECT * FROM orders WHERE user_id = ? AND created_at &gt; ?

图片 / 文件存储
R2

理由:文件太大(25 MB 是 KV 的上限),而且不需要 key-value 的快速读取模式。R2 更适合对象存储场景。

// R2 存储文件
await env.MY_BUCKET.put("images/profile.jpg", imageBuffer);

API Rate Limiting
KV(但要小心)

理由:计数器是 key-value,但写入频率可能超过 1 RPS。如果只是简单的”检查今天的请求次数”,KV 还能用;如果是精确的 per-second rate limiting,可能需要 Durable Objects 或 Upstash Redis。

// 简单的 rate limiting(每天刷新)
const count = parseInt(await env.KV.get(`rate:${userId}`) || "0");
if (count &gt; 100) {
  return new Response("Rate limit exceeded", { status: 429 });
}
await env.KV.put(`rate:${userId}`, String(count + 1));

第三方 API 缓存
KV

理由:API 返回值缓存,读取多,写入少(只在 API 调用失败或过期时更新)。5 分钟 TTL 的缓存完全不需要即时一致性。

组合使用的例子

很多时候,一个项目会组合使用多种存储:

interface Env {
  SESSION_KV: KVNamespace;   // 用户 session
  CACHE_KV: KVNamespace;     // API 缓存
  DATABASE_D1: D1Database;   // 用户数据、订单
  FILES_R2: R2Bucket;        // 用户上传的文件
}

// 一个请求可能用到全部:
// 1. 从 SESSION_KV 读取 session
// 2. 从 CACHE_KV 读取第三方 API 缓存
// 3. 从 DATABASE_D1 查询用户订单
// 4. 从 FILES_R2 返回用户头像

这种组合使用,才是 Cloudflare 全家桶的真正威力。

性能优化实战技巧

KV 用对了很爽,用错了也可能变成瓶颈。这里分享几个我实测有效的优化技巧。

1. cacheTtl 参数调优

默认的 cacheTtl 是 60 秒。对于热点数据,这个值可以大幅提升。

// ❌ 默认行为:60 秒边缘缓存
await env.KV.get("config:feature-flags");

// ✅ 优化:对于配置类数据,缓存更久
await env.KV.get("config:feature-flags", {
  cacheTtl: 3600, // 1 小时边缘缓存
});

什么场景适合加大 cacheTtl?

  • Feature flags:改了配置,等几分钟生效完全 OK
  • 静态配置:API endpoint、第三方服务 URL
  • 重定向规则:URL 映射表,变化频率低

什么场景不适合?

  • Session data:用户登录状态,需要即时反映
  • 实时计数:限流计数器,需要精确

2. 并行 API 调用而非串行

这是一个很容易踩的坑。如果你的 Worker 需要读取多个 key,不要一个接一个地读。

// ❌ 串行读取:每个请求都要等待上一个完成
const user = await env.KV.get(`user:${userId}`);
const settings = await env.KV.get(`settings:${userId}`);
const permissions = await env.KV.get(`permissions:${userId}`);
// 总延迟 = 3 × 单次延迟

// ✅ 并行读取:三个请求同时发出
const [user, settings, permissions] = await Promise.all([
  env.KV.get(`user:${userId}`),
  env.KV.get(`settings:${userId}`),
  env.KV.get(`permissions:${userId}`),
]);
// 总延迟 ≈ 单次延迟(最慢的那个)

KV 的 API 调用是异步的,不会阻塞 Worker 执行。用 Promise.all 可以把多个请求的延迟”压缩”到最长那一个。

实测数据:读 3 个 key,串行大概 20ms,并行只有 8ms。

60%
延迟减少(并行 vs 串行)
来源: 实测数据

3. Hot Keys 设计策略

KV 的性能很大程度上取决于你的 key 是否”hot”——是否频繁被访问。

避免 cold keys 的策略

// ❌ key 太分散,每个用户只访问自己的 key
await env.KV.get(`session:${userId}`); // 只有这个用户访问,cold key

// ✅ 合并成 hot key(适用于共享数据)
await env.KV.get("config:global-flags"); // 所有用户共享,hot key

但这不意味着你要把所有数据塞到一个 key 里。正确的做法是:

  • 用户私有数据:按用户 ID 分 key(session、profile)
  • 全局共享数据:用单一 hot key(配置、flags、重定向规则)

4. Namespace 组织最佳实践

账户可以有 1000 个 namespace。用好这个限制,可以帮你隔离不同类型的数据。

# wrangler.toml
[[kv_namespaces]]
binding = "SESSION_KV"
id = "xxx"  # 用户 session

[[kv_namespaces]]
binding = "CACHE_KV"
id = "yyy"  # API 缓存

[[kv_namespaces]]
binding = "CONFIG_KV"
id = "zzz"  # 配置数据

好处:

  1. 隔离清理:CACHE_KV 可以批量清理,不影响 SESSION_KV
  2. 不同 TTL 策略:SESSION 用短 TTL,CONFIG 用长 TTL
  3. 监控分离:Cloudflare Dashboard 可以分开看每个 namespace 的用量

5. 批量操作技巧

KV 支持 list() 操作,可以按前缀查询所有 key:

// 列出所有 session keys
const result = await env.SESSION_KV.list({ prefix: "session:" });

// result.keys 是一个数组
for (const key of result.keys) {
  console.log(key.name);
}

// 如果 key 很多,会有 cursor 分页
if (!result.list_complete) {
  const next = await env.SESSION_KV.list({
    prefix: "session:",
    cursor: result.cursor,
  });
}

批量清理过期 session:

// 清理所有 session(谨慎使用)
const keys = await env.SESSION_KV.list({ prefix: "session:" });
for (const key of keys.keys) {
  await env.SESSION_KV.delete(key.name);
}

注意:批量删除操作要谨慎,会消耗大量写入额度。

定价与限制 — 成本控制指南

KV 的定价很友好,但有几个限制要特别注意。如果踩坑了,可能直接报错。

100,000
免费读取/天
1,000
免费写入/天
1 GB
免费存储空间
$5
Paid Plan 月费
数据来源: Cloudflare 定价页

Free Plan vs Paid Plan

指标Free PlanPaid Plan($5/month)
读取次数100,000 / day无限(计费)
写入次数1,000 / day无限(计费)
删除次数1,000 / day无限(计费)
列出次数1,000 / day无限(计费)
存储空间1 GB无限(计费)
Namespace 数量10001000

Free plan 对于个人项目、测试完全够用。生产环境建议用 Paid plan——$5 一个月,能换来:

  • 无读取限制(只按量计费)
  • 更多写入额度
  • Dashboard 监控和告警

Write Rate Limit:最关键的限制

这是 KV 最重要的限制,也是最容易踩坑的地方:

每个 unique key,每秒最多写入 1 次(1 RPS)

超过这个限制,请求会直接报错。

// ❌ 高频写入会失败
for (let i = 0; i &lt; 10; i++) {
  await env.KV.put("counter", String(i)); // 第 2+ 次会失败
}

// ✅ 分散 key 可以绕过限制
await env.KV.put(`counter:${Math.floor(Date.now() / 1000)}`, value);
// 每秒一个新 key,不会触发限制

这个限制的本质是什么?KV 的架构是”写一次,复制到全球”。频繁写入同一个 key,复制开销会爆炸。所以 Cloudflare 用这个限制保护自己的系统。

应对策略

  1. 用时间分散 keycounter:timestamp 每秒一个新 key
  2. 用 UUID 分散:每次写入用新 UUID 作为 key
  3. 改用 D1 或 Durable Objects:如果高频写入是刚需

Value Size 限制

KV 的 value 最大 25 MB(2025 年初从 10 MB 提升)。

// ❌ 超过 25 MB 会报错
const largeData = generateBigString(30_000_000); // 30 MB
await env.KV.put("large-key", largeData); // Error!

// ✅ 大数据用 R2
await env.R2_BUCKET.put("large-key", largeData);

25 MB 对于 session、配置、缓存来说绰绰有余。但如果你要存大型 JSON 或文件,R2 是更好的选择。

Namespace 管理策略

账户总共 1000 个 namespace。2025 年初从 200 提升到这个数字,说明 Cloudflare 在放宽限制。

管理策略

// 按功能分组
SESSION_KV    // 用户 session
CACHE_KV      // API 缓存
CONFIG_KV     // 配置数据
RATE_LIMIT_KV // 限流计数

如果 namespace 用完了怎么办?可以用 key 前缀在同一个 namespace 里隔离:

// 单 namespace 内隔离
await env.KV.put("session:user1", data);
await env.KV.put("cache:api1", data);
await env.KV.put("config:flags", data);

成本估算公式

如果你的项目用 Paid plan:

月成本 = $5(基础费)+ 读取费用 + 写入费用 + 存储费用

读取费用 = 读取次数 × $0.01 / 100,000
写入费用 = 写入次数 × $1.00 / 1,000,000
存储费用 = 存储大小 × $0.50 / GB

举例:一个每天 10万请求的项目:

  • 读取:100,000 × 30 = 3M reads/month = $0.30
  • 写入:假设 1000 × 30 = 30k writes/month ≈ $0.03
  • 存储:10 MB × $0.50/GB ≈ $0.005
  • 总成本:$5 + $0.33 ≈ $5.35/month

很便宜,对吧?这是 Cloudflare 的典型定价风格。

总结

说了这么多,核心就一句话:KV 是 Workers 的”随身内存”,适合 session、cache、配置这些读多写少的场景。

给你一个快速决策清单:

用 KV,如果

  • 数据是简单的 key-value
  • 读取频率远高于写入
  • 不需要即时一致性
  • 每个 key 每秒写入不超过 1 次

换 D1,如果

  • 需要 SQL 查询
  • 有复杂的表关联
  • 写入频率可能超过 1 RPS

换 R2,如果

  • 存文件、图片、视频
  • value 超过 25 MB

换 Durable Objects,如果

  • 需要即时一致性
  • 协作编辑、实时同步

下一步?试试把你的 Workers 项目接入 KV。先从 session storage 开始——上面的代码可以直接跑起来。遇到问题,Cloudflare 官方文档写得挺详细,或者直接搜这篇系列的其他文章。

如果你对 D1 或 R2 也感兴趣,可以看看 cloudflare-bindui 系列里其他篇——我会在那里把完整存储矩阵都讲清楚。

常见问题

Cloudflare Workers KV 的写入限制是什么?
每个 unique key 每秒最多写入 1 次(1 RPS)。超过这个限制,请求会直接报错。应对策略:用时间戳分散 key(如 counter:timestamp),或改用 D1/Durable Objects。
KV 适合存储用户 session 吗?
非常适合。Session 数据是简单的 key-value,读取频率高(每次请求验证),写入频率低(只在登录/登出)。配合 TTL 自动过期,无需手动清理。
KV 和 D1 有什么区别?我该选哪个?
核心区别:

• KV:Key-Value 模型,hot keys 延迟 500µs-10ms,写入限制 1 RPS/key
• D1:SQL 模型(SQLite),支持复杂查询,无写入限制

选择:需要 SQL 查询选 D1,简单 key-value 且读多写少选 KV。
KV 的延迟为什么能达到 500µs-10ms?
三层缓存架构:Edge Cache(边缘节点)→ Regional Cache → Central Store。约 30% 请求直接在边缘缓存命中,不用回源。2025 年 Cloudflare 优化后速度提升 3 倍。
cacheTtl 参数有什么作用?
控制边缘缓存的存活时间。默认 60 秒。对于热点数据(如 feature flags、配置),可设置为 3600 秒(1 小时),让边缘节点缓存更久,减少回源。
KV 的 value 最大能存多大?
25 MB(2025 年初从 10 MB 提升)。超过这个限制会报错,大数据(图片、视频)应使用 R2 Object Storage。

16 分钟阅读 · 发布于: 2026年4月22日 · 修改于: 2026年4月25日

相关文章

BetterLink

想持续收到这个主题的更新?

你可以直接关注作者更新、订阅 RSS,或者继续沿着系列入口往下读,避免下次又回到搜索结果重新找。

关注公众号

评论

使用 GitHub 账号登录后即可评论