Cloudflare Workers KV 存储
上一篇的 CRUD 接口用的是内存数组,数据无法持久化。这一篇引入 Workers KV,一个全球分布式键值存储,让数据真正落地。
KV 是什么
KV 的全称是 Key-Value,就是一个超级大的 Map,通过键存取值
"user:1" -> {"name": "Alice", "age": 20}
"config" -> {"theme": "dark"}
它的特性如下
| 特性 | 说明 |
|---|---|
| 全球分布 | 数据自动复制到全球所有边缘节点 |
| 读写极快 | 读取由就近节点直接响应 |
| 最终一致性 | 写入后约 60 秒内传播到全球 |
| 支持过期时间 | 键可以设置 TTL,到期自动删除 |
| 单值上限 | 最大 25MB |
KV 最适合读多写少的场景,典型用途是存储配置、功能开关、用户资料缓存、静态内容。
适用场景与注意点
KV 的最终一致性是新手最容易踩的坑。你在某个节点写入数据后,其他地区的节点可能要过一段时间才能读到新值。所以它不适合需要强一致性的业务,比如库存扣减、计数器、订单状态。
| 适合 KV | 不适合 KV |
|---|---|
| 站点配置 | 计数累加 |
| 用户资料缓存 | 库存扣减 |
| 静态内容 | 交易记录 |
| 功能开关 | 需要立刻读到最新值的场景 |
创建命名空间
KV 的数据存放在”命名空间”里,每个命名空间是一个独立的数据集合。一个 Worker 可以绑定多个命名空间。
用命令行创建
wrangler kv namespace create MY_KV
命令执行后输出类似
🌀 Creating namespace with title "worker-MY_KV"
✨ Success!
id = "e29b263ab50e42ce9b637fa8370175e8"
记下这个 id,也可以去仪表盘的 KV 页面创建,效果相同。
绑定到 Worker
在 wrangler.toml 中添加绑定配置
name = "my-worker"
main = "src/index.js"
compatibility_date = "2026-01-01"
[[kv_namespaces]]
binding = "MY_KV"
id = "e29b263ab50e42ce9b637fa8370175e8"
binding 是代码里的变量名,id 是刚创建的命名空间 id。配置后,代码里就可以用 env.MY_KV 访问。
基础读写删操作
export default {
async fetch(request, env, ctx) {
// 读取
const value = await env.MY_KV.get("user:1");
// 写入
await env.MY_KV.put("user:1", JSON.stringify({ name: "Alice" }));
// 删除
await env.MY_KV.delete("user:1");
// 按前缀列出键
const list = await env.MY_KV.list({ prefix: "user:" });
return Response.json({ value, list });
},
};
常用方法一览
| 方法 | 说明 |
|---|---|
get(key) |
读取值,不存在时返回 null |
put(key, value) |
写入或覆盖 |
delete(key) |
删除 |
list(options) |
按前缀等条件列出键 |
getWithMetadata(key) |
读取值并附带元数据 |
KV 的值只能是字符串。存对象时用 JSON.stringify 序列化,读取时用 JSON.parse 反序列化。
数据过期设置
写入时可以设置过期时间,让 KV 自动清理数据
// 10 分钟(600 秒)后过期
await env.MY_KV.put("captcha:123", "abcd", { expirationTtl: 600 });
// 指定绝对过期时间戳
await env.MY_KV.put("promo", "summer", { expiration: 1790000000 });
expirationTtl 的单位是秒,从写入时刻开始计算。注意 TTL 有 60 秒的最小值。
接入上篇的 CRUD 接口
把上一篇的内存数组换成 KV,接口立刻变成持久化版本
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
const parts = url.pathname.split("/").filter(Boolean);
if (!(parts[0] === "api" && parts[1] === "users")) {
return new Response("页面不存在", { status: 404 });
}
const id = parts[2];
const method = request.method;
// 读取单个用户
if (method === "GET" && id) {
const raw = await env.MY_KV.get(`user:${id}`);
if (!raw) return Response.json({ error: "用户不存在" }, { status: 404 });
return Response.json(JSON.parse(raw));
}
// 新增用户
if (method === "POST") {
const body = await request.json();
const newId = String(Date.now());
await env.MY_KV.put(`user:${newId}`, JSON.stringify(body));
return Response.json({ id: newId, ...body }, { status: 201 });
}
// 删除用户
if (method === "DELETE" && id) {
await env.MY_KV.delete(`user:${id}`);
return Response.json({ ok: true });
}
return Response.json({ error: "请求方式不支持" }, { status: 405 });
},
};
键命名建议用前缀加分隔符,例如 user:1、config:theme,方便用 list({ prefix: "user:" }) 按类型管理。
免费额度提醒
KV 免费计划按日计量,每天读 10 万次、写 1 千次、删除和列出各 1 千次。学习阶段用量很小,不用担心,但接口上线后要留意仪表盘的用量统计。
KV 解决了键值存储,但遇到需要 SQL 查询、关联表结构的场景就力不从心了。下一篇讲 D1 关系型数据库,并实现 KV 与 D1 搭配的缓存方案。
上一篇 Cloudflare Workers 环境变量与密钥管理
下一篇 Cloudflare Workers D1 数据库