大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
Workers KV
这一课你将能做到
- 创建 KV Namespace 并在 Worker 中完成读、写、列、删操作
- 根据业务场景选择正确的 KV 缓存模式(TTL、元数据、列表前缀)
- 理解 KV 的「最终一致性」语义,并能判断它和 Redis 的适用边界
为什么要学
你的 Next.js 前端需要一份「全站配置」来控制导航栏、Banner 文案、全局开关。如果每次请求都去读数据库,中心机房延迟 80ms,用户体验就崩了。如果放进 Redis,运维又要多维护一套集群。KV 就是专为这种「读多写少、边缘就近」的场景设计的。
人话:KV 是一个全球分布的键值存储。你写一次,Cloudflare 会自动把数据同步到全球所有边缘节点。下次用户在北京请求,直接从北京的节点读到值——不是去中心机房,不是去 Redis,而是从本地缓存读。
先建立图像
写入: [Worker] ── PUT ──> [KV 中心存储]
│
异步复制到全球节点
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
[北京节点] [纽约节点] [伦敦节点]
│ │ │
读取: 5ms 读取: 8ms 读取: 6ms
这就是 KV 的核心特点:写入后最终一致,但读取极快。
跟着做
步骤 1:创建 Namespace
npx wrangler kv namespace create "MY_KV"
成功后你会得到一段 toml 配置,把它复制到 wrangler.toml:
[[kv_namespaces]]
binding = "MY_KV"
id = "0xxxxxxxxxxxxxxxxxxxxxxxxx"
步骤 2:在 Worker 中读写
export interface Env {
MY_KV: KVNamespace;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
const key = url.searchParams.get('key') ?? 'default';
if (request.method === 'GET') {
const value = await env.MY_KV.get(key);
return value ? Response.json({ key, value }) : new Response('Not Found', { status: 404 });
}
if (request.method === 'PUT') {
const body = await request.text();
await env.MY_KV.put(key, body, { expirationTtl: 3600 }); // 1 小时 TTL
return new Response('OK', { status: 201 });
}
if (request.method === 'DELETE') {
await env.MY_KV.delete(key);
return new Response('Deleted');
}
return new Response('Method Not Allowed', { status: 405 });
},
} satisfies ExportedHandler<Env>;
步骤 3:带元数据的写入
await env.MY_KV.put(key, body, {
expirationTtl: 86400, // 24 小时后过期
metadata: { updatedBy: 'admin', version: '2' },
});
步骤 4:列表(前缀扫描)
const list = await env.MY_KV.list({ prefix: 'config:' });
for (const key of list.keys) {
const value = await env.MY_KV.get(key.name);
console.log(key.name, value, key.metadata);
}
步骤 5:通过 CLI 操作 KV
# 写入
npx wrangler kv key put --binding=MY_KV "hello" "world"
# 读取
npx wrangler kv key get --binding=MY_KV "hello"
# 列出前缀为 config: 的键
npx wrangler kv key list --binding=MY_KV --prefix="config:"
# 批量删除
npx wrangler kv key delete --binding=MY_KV "hello"
讲清楚要点
- KV 不是 Redis:KV 不支持原子操作、事务、计数器递增、列表类型。它就是一个「带全球 CDN 的字符串键值存储」。
- 写入是最终一致:你刚写了一个 key,马上读可能读到旧值( propagation delay 通常在秒级)。如果你需要强一致,用 D1 或 Durable Objects。
- TTL 以秒为单位:支持
expiration(绝对时间戳)和expirationTtl(相对秒数)。过期后 key 自动删除,不收费。 - value 最大 25MB:超过的话用 R2 存大文件,KV 里只存引用 URL 或元数据。
- 列表有分页:
list()默认返回 1000 条,有cursor分页机制。大规模扫描时要注意 API 限流。
你可能会懵的地方
-
刚 PUT 的数据 GET 不到? 正常。KV 的写入是先写中心,再异步复制到全球节点。刚写完立刻读,可能打到还没同步的节点。需要强一致就用 D1。
-
KV 支持 JSON 吗? value 只能是字符串。存 JSON 时
JSON.stringify()写入,JSON.parse()读出。 -
怎么统计某个前缀有多少 key? KV 没有 count API。只能
list()遍历,但要注意分页。如果 key 很多,考虑用 D1 存计数器。 -
免费额度够吗? 免费套餐:每天 100,000 次读取、1,000 次写入、1,000 次删除。个人项目通常够用,高并发写场景需要付费。
-
KV 和 Cache API 有什么区别? Cache API 是 HTTP 缓存,按 URL 存储 Response 对象,适合 HTTP 响应缓存。KV 是键值存储,适合配置、状态、小数据。两者可以配合使用。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
把 KV 当 Redis 做 INCR 计数器 | KV 没有原子递增操作 | 计数器用 D1 或 Durable Objects |
| 写完后立刻读,期望强一致 | KV 是最终一致 | 对一致性有要求的用 D1 |
value 直接存对象 put(key, obj) | value 必须是字符串 | 先 JSON.stringify(obj) |
列表时不处理 cursor 分页 | 超过 1000 条时丢数据 | while (list.cursor) { list = await kv.list({ cursor: list.cursor }) } |
| key 名没有命名空间前缀 | 容易冲突,难以扫描 | 用 user:123:profile 这类结构化 key |
卡点急救
env.MY_KV is undefined:检查wrangler.toml的binding名和代码里是否一致;检查wrangler dev是否重启(修改 toml 后要重启)。list()返回空但 key 存在:确认prefix参数是否匹配;确认 list 的是同一个 namespace ID。- 写入 413 Payload Too Large:单个 value 超过 25MB。拆分数据或改用 R2。
面试常问
-
KV 和 Redis 怎么选? KV 适合「全球边缘、读多写少、最终一致」的配置和缓存场景;Redis 适合「复杂数据结构、强一致、高并发写、Lua 脚本」的场景。如果应用需要原子计数、Pub/Sub、Sorted Set,用 Redis 或 Cloudflare Queues。
-
KV 的一致性模型是什么? 最终一致性(Eventual Consistency)。写入后会在秒级内传播到全球节点,但在此期间不同节点可能读到不同值。读取总是从最近的边缘节点进行。
-
KV 的计费模式? 按请求次数 + 存储量。读取比写入便宜得多(约 10:1),设计时尽量多读少写,批量写比多次单写更划算。
自测三问
- KV 的写入和读取延迟分别是多少?为什么写入后不能立刻读到?
- 如果要在 KV 里存一个 30MB 的 JSON 文件,应该怎么处理?
- KV 的
list()有什么限制?怎么遍历超过 1000 条的数据?
小结一句话
Workers KV 是边缘场景的「最终一致」键值存储:全球就近读取极快,适合配置、缓存、小数据,但不适合强一致写和复杂数据结构。
延伸
- Workers KV 官方文档
- KV Pricing & Limits
- 下一课:R2 对象存储,学习 S3 兼容的边缘存储与生命周期管理。