关于 cloudflare-kv
这项技能面向使用 Cloudflare Workers KV 的开发者,介绍键值操作、缓存、分页、一致性行为、Wrangler 命令和使用限制。
主要功能
- 01读取、写入、列出和删除键
- 02缓存与元数据模式
- 03分页列出键
- 04一致性与速率限制指南
- 05Wrangler CLI 命令示例
适用场景
- 01缓存频繁读取的数据
- 02存储配置和功能标志
- 03按前缀分页遍历键
- 04本地开发时连接远程命名空间
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jezweb/ claude-skills --skill cloudflare-kv安装后在 Claude Code 中输入/cloudflare-kv确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.md.claude-plugin/plugin.jsonreferences/best-practices.mdworkers-api.mdrules/cloudflare-kv.mdtemplates/kv-basic-operations.tskv-caching-pattern.tskv-list-pagination.tskv-metadata-pattern.ts详细介绍
Cloudflare Workers KV
状态:已可用于生产环境 ✅ 最后更新:2026-01-20 依赖项:cloudflare-worker-base(用于 Worker 设置) 最新版本:[email protected],@cloudflare/[email protected]
近期更新(2025):
- 2025 年 8 月:架构重新设计(性能提升 40 倍,p99 延迟 <5ms,采用与 R2 混合存储)
- 2025 年 4 月:批量读取 API(单次请求最多检索 100 个键,计为 1 次操作)
- 2025 年 1 月:命名空间限额提高(Free 和 Paid 套餐的每个账户命名空间数从 200 个增至 1,000 个)
快速入门(5 分钟)
# Create namespace
npx wrangler kv namespace create MY_NAMESPACE
# Output: [[kv_namespaces]] binding = "MY_NAMESPACE" id = "<UUID>"
wrangler.jsonc:
{
"kv_namespaces": [{
"binding": "MY_NAMESPACE", // Access as env.MY_NAMESPACE
"id": "<production-uuid>",
"preview_id": "<preview-uuid>" // Optional: local dev
}]
}
基本用法:
type Bindings = { MY_NAMESPACE: KVNamespace };
app.post('/set/:key', async (c) => {
await c.env.MY_NAMESPACE.put(c.req.param('key'), await c.req.text());
return c.json({ success: true });
});
app.get('/get/:key', async (c) => {
const value = await c.env.MY_NAMESPACE.get(c.req.param('key'));
return value ? c.json({ value }) : c.json({ error: 'Not found' }, 404);
});
KV API 参考
读取操作
// Get single key
const value = await env.MY_KV.get('key'); // string | null
const data = await env.MY_KV.get('key', { type: 'json' }); // object | null
const buffer = await env.MY_KV.get('key', { type: 'arrayBuffer' });
const stream = await env.MY_KV.get('key', { type: 'stream' });
// Get with cache (minimum 60s)
const value = await env.MY_KV.get('key', { cacheTtl: 300 }); // 5 min edge cache
// Bulk read (counts as 1 operation)
const values = await env.MY_KV.get(['key1', 'key2']); // Map<string, string | null>
// With metadata
const { value, metadata } = await env.MY_KV.getWithMetadata('key');
const result = await env.MY_KV.getWithMetadata(['key1', 'key2']); // Bulk with metadata
写入操作
// Basic write (max 1/second per key)
await env.MY_KV.put('key', 'value');
await env.MY_KV.put('user:123', JSON.stringify({ name: 'John' }));
// With expiration
await env.MY_KV.put('session', data, { expirationTtl: 3600 }); // 1 hour
await env.MY_KV.put('token', value, { expiration: Math.floor(Date.now()/1000) + 86400 });
// With metadata (max 1024 bytes)
await env.MY_KV.put('config', 'dark', {
metadata: { updatedAt: Date.now(), version: 2 }
});
关键限制:
- 键:最多 512 字节
- 值:最多 25 MiB
- 元数据:最多 1024 字节
- 写入速率:每个键每秒 1 次(超出时返回 429 错误)
- 过期时间:最短 60 秒
列表操作
// List with pagination
const result = await env.MY_KV.list({ prefix: 'user:', limit: 1000, cursor });
// result: { keys: [], list_complete: boolean, cursor?: string }
// CRITICAL: Always check list_complete, not keys.length === 0
let cursor: string | undefined;
do {
const result = await env.MY_KV.list({ prefix: 'user:', cursor });
processKeys(result.keys);
cursor = result.list_complete ? undefined : result.cursor;
} while (cursor);
删除操作
// Delete single key
await env.MY_KV.delete('key'); // Always succeeds
// Bulk delete (CLI only, up to 10,000 keys)
// npx wrangler kv bulk delete --binding=MY_KV keys.json
高级模式
使用 CacheTtl 的缓存模式
async function getCachedData(kv: KVNamespace, key: string, fetchFn: () => Promise<any>, ttl = 300) {
const cached = await kv.get(key, { type: 'json', cacheTtl: ttl });
if (cached) return cached;
const data = await fetchFn();
await kv.put(key, JSON.stringify(data), { expirationTtl: ttl * 2 });
return data;
}
指南:最短为 60 秒,适用于读取密集型工作负载(读写比为 100:1)
元数据优化
// Store small values (<1024 bytes) in metadata to avoid separate get() calls
await env.MY_KV.put('user:123', '', {
metadata: { status: 'active', plan: 'pro', lastSeen: Date.now() }
});
// list() returns metadata automatically (no additional get() calls)
const users = await env.MY_KV.list({ prefix: 'user:' });
users.keys.forEach(({ name, metadata }) => console.log(name, metadata.status));
了解热键与冷键
KV 性能会因键的热度而异:
| 类型 | 响应时间 | 发生情况 |
|---|---|---|
| 热键 | 6-8ms | 每个数据中心每分钟读取 2 次或以上 |
| 冷键 | 100-300ms | 不常访问,从中央存储获取 |
2025 年 8 月之后的改进:
- 所有 KV Worker 调用的 P90:<12ms(此前为 22ms)
- 热读取速度最高提升 3 倍
- 所有操作最多快 20ms
优化:使用键合并,让冷键也能受益于热键缓存:
// ❌ Bad: Many cold keys (300ms each)
await kv.put('user:123:name', 'John');
await kv.put('user:123:email', '[email protected]');
await kv.put('user:123:plan', 'pro');
// Each read of a cold key: ~100-300ms
const name = await kv.get('user:123:name'); // Cold
const email = await kv.get('user:123:email'); // Cold
const plan = await kv.get('user:123:plan'); // Cold
// ✅ Good: Single hot key (6-8ms)
await kv.put('user:123', JSON.stringify({
name: 'John',
email: '[email protected]',
plan: 'pro'
}));
// Single read, cached as hot key: ~6-8ms
const user = JSON.parse(await kv.get('user:123'));
CacheTtl 有助于冷键:对于读取频率较低的数据,cacheTtl 可降低冷读取延迟。
权衡:合并更新时需要先读取,再修改并写入
分页辅助工具
async function* paginateKV(kv: KVNamespace, options: { prefix?: string } = {}) {
let cursor: string | undefined;
do {
const result = await kv.list({ ...options, cursor });
yield result.keys;
cursor = result.list_complete ? undefined : result.cursor;
} while (cursor);
}
// Usage
for await (const keys of paginateKV(env.MY_KV, { prefix: 'user:' })) {
processKeys(keys);
}
使用指数退避重试速率限制错误
async function putWithRetry(kv: KVNamespace, key: string, value: string, opts?: KVPutOptions) {
let attempts = 0, delay = 1000;
while (attempts < 5) {
try {
await kv.put(key, value, opts);
return;
} catch (error) {
if ((error as Error).message.includes('429')) {
attempts++;
if (attempts >= 5) throw new Error('Max retry attempts');
await new Promise(r => setTimeout(r, delay));
delay *= 2; // Exponential backoff
} else throw error;
}
}
}
了解最终一致性
KV 在 Cloudflare 的全球网络中具有最终一致性(2025 年 8 月重新设计:混合存储,p99 延迟 <5ms):
工作原理:
- 写入会立即在同一位置可见(在同一 POP 内可读到自己的写入)
- 其他位置会在约 60 秒内看到更新(或在
cacheTtl指定的时间内) - 在传播期间,缓存读取可能会返回过期数据
示例:
// Tokyo: Write
await env.MY_KV.put('counter', '1');
const value = await env.MY_KV.get('counter'); // "1" ✅ (same POP, RYOW)
// London (within 60s): May be stale ⚠️
const value2 = await env.MY_KV.get('counter'); // Might be old value
// After 60+ seconds: Consistent ✅
读后写一致性(RYOW)保证:自 2025 年 8 月重新设计以来,经由同一 Cloudflare 接入点路由的请求会立即看到自己的写入。不同 POP 之间的全局一致性仍需最多 60 秒。
时间戳缓解模式(适用于一致性要求较高的场景):
// Use timestamp in key structure to avoid consistency issues
const timestamp = Date.now();
await kv.put(`user:123:${timestamp}`, userData);
// Find latest using list with prefix
const result = await kv.list({ prefix: 'user:123:' });
const latestKey = result.keys.sort((a, b) =>
parseInt(b.name.split(':')[2]) - parseInt(a.name.split(':')[2])
).at(0);
适用于 KV 的场景:读取密集型工作负载(读写比为 100:1)、配置、功能标志、缓存、用户偏好设置 不适用于 KV 的场景:金融交易、强一致性需求、每个键每秒写入超过 1 次、关键数据
**需要强一致性?**请使用 Durable Objects
Wrangler CLI 基础知识
# Create namespace
npx wrangler kv namespace create MY_NAMESPACE [--preview]
# Manage keys (add --remote flag to access production data)
npx wrangler kv key put --binding=MY_KV "key" "value" [--ttl=3600] [--metadata='{}']
npx wrangler kv key get --binding=MY_KV "key" [--remote]
npx wrangler kv key list --binding=MY_KV [--prefix="user:"] [--remote]
npx wrangler kv key delete --binding=MY_KV "key"
# Bulk operations (up to 10,000 keys)
npx wrangler kv bulk put --binding=MY_KV data.json
npx wrangler kv bulk delete --binding=MY_KV keys.json
重要:CLI 命令默认使用本地存储。添加 --remote 标志以访问生产环境/远程数据。
开发环境与生产环境
本地开发的远程绑定(Wrangler 4.37+)
在开发期间,将本地 Workers 连接到生产环境的 KV 命名空间:
wrangler.jsonc:
{
"kv_namespaces": [{
"binding": "MY_KV",
"id": "production-uuid",
"remote": true // Connect to live KV
}]
}
工作原理:
- 本地 Worker 代码在本地执行(快速迭代)
- KV 操作通过代理路由到生产环境的命名空间
- 无需手动填充数据
优势:
- 无需部署即可使用真实的生产数据进行测试
- 本地代码执行速度快,同时可访问生产数据
- 更快地获得反馈(无需经历部署-测试周期)
⚠️ 警告:写入操作会影响生产数据。请考虑使用设置了 remote: true 的暂存命名空间,而不是生产环境。
版本支持:
- Wrangler 4.37.0+
- @cloudflare/vite-plugin 1.13.0+
- @cloudflare/vitest-pool-workers 0.9.0+
来源:远程绑定架构
限制与配额
| 功能 | 免费计划 | 付费计划 |
|---|---|---|
| 每日读取次数 | 100,000 | 无限制 |
| 每日写入次数(不同键) | 1,000 | 无限制 |
| 每个键每秒写入次数 | 1 | 1 |
| 每次 Worker 调用的操作数 | 1,000 | 1,000 |
| 每个账户的命名空间数 | 1,000 | 1,000 |
| 每个账户的存储空间 | 1 GB | 无限制 |
| 键大小 | 512 字节 | 512 字节 |
| 元数据大小 | 1024 字节 | 1024 字节 |
| 值大小 | 25 MiB | 25 MiB |
| 最小 cacheTtl | 60 秒 | 60 秒 |
重要提示:每个键每秒最多写入 1 次(超出会返回 429),批量操作计为 1 次操作;命名空间上限从 200 提高到 1,000(2025 年 1 月)
错误处理
1. 速率限制(429 Too Many Requests)
原因:向同一个键每秒写入超过 1 次 解决方案:使用带指数退避的重试(参见高级模式)
// ❌ Bad
await env.MY_KV.put('counter', '1');
await env.MY_KV.put('counter', '2'); // 429 error!
// ✅ Good
await putWithRetry(env.MY_KV, 'counter', '2');
2. 值过大
原因:值超过 25 MiB 解决方案:写入前验证大小
if (value.length > 25 * 1024 * 1024) throw new Error('Value exceeds 25 MiB');
3. 元数据过大
原因:序列化后的元数据超过 1024 字节 解决方案:验证序列化后的大小
const serialized = JSON.stringify(metadata);
if (serialized.length > 1024) throw new Error('Metadata exceeds 1024 bytes');
4. CacheTtl 无效
原因:cacheTtl <60 秒 解决方案:使用至少 60 秒
// ❌ Error
await env.MY_KV.get('key', { cacheTtl: 30 });
// ✅ Correct
await env.MY_KV.get('key', { cacheTtl: 60 });
重要规则
始终执行 ✅
- 读取多个键时使用批量操作(计为 1 次操作)
- 为频繁读取、很少更新的数据设置 cacheTtl(至少 60 秒)
- 频繁使用
list()时,将小值(<1024 字节)存储在元数据中 - 分页时检查
list_complete,不要检查keys.length === 0 - 写入操作使用带指数退避的重试逻辑
- 写入前验证大小(键 512B、值 25MiB、元数据 1KB)
- 合并相关键以获得更好的缓存性能
- 将 KV 用于读取密集型工作负载(理想的读写比为 100:1)
绝不执行 ❌
- 绝不要每秒向同一个键写入超过 1 次(会导致 429 速率限制错误)
- 绝不要假设会立即实现全局一致性(传播约需 60 秒)
- 绝不要将 KV 用于原子操作(改用 Durable Objects)
- 绝不要将 cacheTtl 设置为小于 60 秒(会失败)
- 绝不要将命名空间 ID 提交到公共仓库(使用环境变量)
- 绝不要在单次调用中超过 1000 次操作(使用批量操作)
- 绝不要依赖写入顺序(最终一致性意味着不作保证)
- 绝不要忘记处理 null 值(键不存在时,
get()会返回null)
故障排除
问题 1:写入时出现“429 Too Many Requests”
原因:向同一个键每秒写入超过 1 次 解决方案:合并写入,或使用带指数退避的重试
// ❌ Bad: Rate limit
for (let i = 0; i < 10; i++) await kv.put('counter', String(i));
// ✅ Good: Single write
await kv.put('counter', '9');
// ✅ Good: Retry with backoff
await putWithRetry(kv, 'counter', String(i));
问题 2:写入后读取到过时数据
原因:最终一致性(传播约需 60 秒) 解决方案:接受读取到过时数据,使用 Durable Objects 实现强一致性,或实现应用级缓存失效
问题 3:“Operations limit exceeded”
原因:单次 Worker 调用中的 KV 操作超过 1000 次 解决方案:使用批量操作
// ❌ Bad: 5000 operations
for (const key of 5000keys) await kv.get(key);
// ✅ Good: 1 operation
const values = await kv.get(keys); // Bulk read
问题 4:列表为空,但游标存在
原因:已删除或过期的键会产生必须遍历的“墓碑”
解决方案:始终检查 list_complete,而不是 keys.length
// ✅ Correct pagination
let cursor: string | undefined;
do {
const result = await kv.list({ cursor });
processKeys(result.keys); // Even if empty
cursor = result.list_complete ? undefined : result.cursor;
} while (cursor);
重要:使用 prefix 时,必须在所有分页调用中包含它:
// ❌ WRONG - Loses prefix on subsequent pages
let result = await kv.list({ prefix: 'user:' });
result = await kv.list({ cursor: result.cursor }); // Missing prefix!
// ✅ CORRECT - Include prefix on every call
let cursor: string | undefined;
do {
const result = await kv.list({ prefix: 'user:', cursor });
processKeys(result.keys);
cursor = result.list_complete ? undefined : result.cursor;
} while (cursor);
来源:键列表文档
问题 5:wrangler types 不会为环境嵌套的 KV 绑定生成类型
原因:在环境配置(例如 [env.feature.kv_namespaces])中定义的 KV 命名空间不会包含在生成的 TypeScript 类型中
影响:KV 绑定失去 TypeScript 自动补全和类型检查
来源:GitHub 问题 #9709
配置示例:
# wrangler.toml
[env.feature]
name = "my-worker-feature"
[[env.feature.kv_namespaces]]
binding = "MY_STORAGE_FEATURE"
id = "xxxxxxxxxxxx"
运行 npx wrangler types 会为环境变量创建类型定义,但不会为 KV 命名空间绑定创建类型定义。
解决方法:
# Generate types for specific environment
npx wrangler types -e feature
或者,将 KV 命名空间定义在顶层,而不是嵌套在环境中:
# Top-level (types generated correctly)
[[kv_namespaces]]
binding = "MY_STORAGE"
id = "xxxxxxxxxxxx"
注意:运行时绑定仍可正常工作;这只会影响类型生成。
问题 6:wrangler kv key list 返回远程数据的空数组
原因:CLI 命令默认使用本地存储,而不是远程/生产 KV 影响:用户期望看到生产数据,却从本地存储中得到空数组 来源:GitHub 问题 #10395
解决方案:使用 --remote 标志访问生产/远程数据
# ❌ Shows local storage (likely empty)
npx wrangler kv key list --binding=MY_KV
# ✅ Shows remote/production data
npx wrangler kv key list --binding=MY_KV --remote
发生原因:按设计,wrangler dev 使用本地 KV 存储,以免影响生产数据。为保持一致性,CLI 命令也采用相同的默认设置。
适用范围:所有 wrangler kv key 命令(get、list、delete、put)
生产环境检查清单
- 已配置环境专属命名空间(
id与preview_id) - 命名空间 ID 存储在环境变量中(未硬编码)
- 已为写入操作实现速率限制重试逻辑
- 已为读取操作设置合适的
cacheTtl值(至少 60s) - 已验证大小限制(键 512B、值 25MiB、元数据 1KB)
- 在可能的情况下使用批量操作
- 分页时检查
list_complete(而不是keys.length) - 已处理空值
- 已针对速率限制设置监控/告警
相关文档
最后更新:2026-01-20
软件包版本:[email protected], @cloudflare/[email protected]
变更:新增 6 项研究发现——热键/冷键性能模式、远程绑定(Wrangler 4.37+)、wrangler types 环境问题、CLI --remote 标志要求、RYOW 一致性详情、分页中的 prefix 持久性