要创建新的键值对,或更新特定键的值,请在已绑定到 Worker 代码的任何 KV 命名空间 的 KV 绑定(binding) 上调用 put() 方法:
env.NAMESPACE.put(key, value);self.env.NAMESPACE.put(key, value)从 Worker 内部写入键值对的示例:
export default {
async fetch(request, env, ctx) {
try {
await env.NAMESPACE.put("first-key", "This is the value for the key");
return new Response("Successful write", {
status: 201,
});
} catch (e) {
return new Response(e.message, { status: 500 });
}
},
};from workers import WorkerEntrypoint, Response
class Default(WorkerEntrypoint):
async def fetch(self, request):
try:
await self.env.NAMESPACE.put("first-key", "This is the value for the key")
return Response("Successful write", status=201)
except Exception as e:
return Response(str(e), status=500)KV 提供以下方法用于写入:
要创建新的键值对,或更新特定键的值,请在已绑定到 Worker 代码的任何 KV 命名空间上调用 put() 方法:
env.NAMESPACE.put(key, value, options?);self.env.NAMESPACE.put(key, value, options)-
key:string- 与值关联的键。键不能为空或完全等于
.或..。所有其他键均有效。键的最大长度为 512 字节。
- 与值关联的键。键不能为空或完全等于
-
value:string|ReadableStream|ArrayBuffer- 要存储的值。类型是推断的。值的最大大小为 25 MiB。
-
options:{ expiration?: number, expirationTtl?: number, metadata?: object }- 可选。包含
expiration(可选)、expirationTtl(可选)和metadata(可选)属性的对象。expiration是表示键值对自 epoch 起多少秒后过期的数字。expirationTtl是表示键值对从现在起多少秒后过期的数字。最小值为 60。metadata是必须序列化为 JSON 的对象。metadata 对象序列化 JSON 表示的最大大小为 1024 字节。
- 可选。包含
response:Promise<void>- 如果更新成功则解析的
Promise。
- 如果更新成功则解析的
put() 方法返回一个 Promise,你应 await 它以验证更新成功。
由于 KV 的最终一致性特性,对同一键的并发写入可能相互覆盖。从 Wrangler、Durable Objects 或 API 的单个进程写入数据是常见模式。这由于单一流而避免竞争的并发写入。所有数据仍可在绑定到命名空间的所有 Workers 中随时访问。
如果对同一键进行并发写入,最后一次写入将优先。
写入在同一全球网络位置的其他请求中立即可见,但在世界其他地区可能需要最多 60 秒(或 get() 或 getWithMetadata() 方法的 cacheTtl 参数值)才能可见。
有关此主题的更多信息,请参阅 KV 工作原理。
使用 Wrangler 或通过 REST API 一次写入多个键值对。
批量 API 一次最多可接受 10,000 个 KV 对。
每个 KV 对都需要 key 和 value。整个请求大小必须小于 100 兆字节。使用 KV 绑定(binding) 不支持批量写入。
KV 提供创建自动过期键的功能。你可以配置在特定时间点过期(使用 expiration 选项),或在键上次修改后经过一定时间后过期(使用 expirationTtl 选项)。
一旦达到过期键的过期时间,它将从系统中删除。删除后,尝试读取该键的行为将与键不存在相同。删除的键不会计入 KV 命名空间的存储用量以用于计费。
有两种方式指定键何时过期:
-
使用 UNIX epoch 以来的秒数 ↗ 指定的绝对时间设置键的过期时间。例如,如果你希望键在 2019 年 4 月 1 日 UTC 12:00AM 过期,你会将键的过期时间设置为
1554076800。 -
使用相对于当前时间的秒数设置键的生存时间(TTL)过期时间。例如,如果你希望键在创建后 10 分钟过期,你会将其 expiration TTL 设置为
600。
不支持距离未来少于 60 秒的过期目标。两种过期方法均如此。
要创建过期键,在 put() 选项中将 expiration 设置为表示自 epoch 起秒数的数字,或在 put() 选项中将 expirationTtl 设置为表示从现在起秒数的数字:
await env.NAMESPACE.put(key, value, {
expiration: secondsSinceEpoch,
});
await env.NAMESPACE.put(key, value, {
expirationTtl: secondsFromNow,
});await self.env.NAMESPACE.put(key, value, expiration=seconds_since_epoch)
await self.env.NAMESPACE.put(key, value, expirationTtl=seconds_from_now)这假设 secondsSinceEpoch/seconds_since_epoch 和 secondsFromNow/seconds_from_now 是在 Worker 代码其他地方定义的变量。
要将元数据与键值对关联,在 put() 选项中将 metadata 设置为对象(可序列化为 JSON):
await env.NAMESPACE.put(key, value, {
metadata: { someMetadataKey: "someMetadataValue" },
});await self.env.NAMESPACE.put(key, value, metadata={"someMetadataKey": "someMetadataValue"})Workers KV 对同一键每秒最多 1 次写入。在 1 秒内对同一键进行的写入将导致抛出速率限制(429)错误。
你不应每秒对同一键写入超过一次。请考虑将在 Worker 调用中对键的写入合并为单次写入,或在写入之间至少等待 1 秒。
以下示例演示了如何在单个 Worker 调用中强制并发写入,从而展示对同一键的多次写入可能返回错误。这不是应在生产中使用的模式。
export default {
async fetch(request, env, ctx): Promise<Response> {
// Rest of code omitted
const key = "common-key";
const parallelWritesCount = 20;
// Helper function to attempt a write to KV and handle errors
const attemptWrite = async (i: number) => {
try {
await env.YOUR_KV_NAMESPACE.put(key, `Write attempt #${i}`);
return { attempt: i, success: true };
} catch (error) {
// An error may be thrown if a write to the same key is made within 1 second with a message. For example:
// error: {
// "message": "KV PUT failed: 429 Too Many Requests"
// }
return {
attempt: i,
success: false,
error: { message: (error as Error).message },
};
}
};
// Send all requests in parallel and collect results
const results = await Promise.all(
Array.from({ length: parallelWritesCount }, (_, i) =>
attemptWrite(i + 1),
),
);
// Results will look like:
// [
// {
// "attempt": 1,
// "success": true
// },
// {
// "attempt": 2,
// "success": false,
// "error": {
// "message": "KV PUT failed: 429 Too Many Requests"
// }
// },
// ...
// ]
return new Response(JSON.stringify(results), {
headers: { "Content-Type": "application/json" },
});
},
};from workers import WorkerEntrypoint, Response
import asyncio
class Default(WorkerEntrypoint):
async def fetch(self, request):
key = "common-key"
parallel_writes_count = 20
async def attempt_write(i):
try:
await self.env.YOUR_KV_NAMESPACE.put(key, f"Write attempt #{i}")
return {"attempt": i, "success": True}
except Exception as error:
# An error may be thrown if a write to the same key is made
# within 1 second with a message like:
# "KV PUT failed: 429 Too Many Requests"
return {"attempt": i, "success": False, "error": {"message": str(error)}}
results = await asyncio.gather(
*[attempt_write(i + 1) for i in range(parallel_writes_count)]
)
# Results will look like:
# [
# {
# "attempt": 1,
# "success": True
# },
# {
# "attempt": 2,
# "success": False,
# "error": {
# "message": "KV PUT failed: 429 Too Many Requests"
# }
# },
# ...
# ]
return Response.json(list(results))要处理这些错误,我们建议实现带指数退避的重试逻辑。以下是为上述代码添加重试的简单方法。
export default {
async fetch(request, env, ctx): Promise<Response> {
// Rest of code omitted
const key = "common-key";
const parallelWritesCount = 20;
// Helper function to attempt a write to KV with retries
const attemptWrite = async (i: number) => {
return await retryWithBackoff(async () => {
await env.YOUR_KV_NAMESPACE.put(key, `Write attempt #${i}`);
return { attempt: i, success: true };
});
};
// Send all requests in parallel and collect results
const results = await Promise.all(
Array.from({ length: parallelWritesCount }, (_, i) =>
attemptWrite(i + 1),
),
);
return new Response(JSON.stringify(results), {
headers: { "Content-Type": "application/json" },
});
},
};
async function retryWithBackoff(
fn: Function,
maxAttempts = 5,
initialDelay = 1000,
) {
let attempts = 0;
let delay = initialDelay;
while (attempts < maxAttempts) {
try {
// Attempt the function
return await fn();
} catch (error) {
// Check if the error is a rate limit error
if (
(error as Error).message.includes(
"KV PUT failed: 429 Too Many Requests",
)
) {
attempts++;
if (attempts >= maxAttempts) {
throw new Error("Max retry attempts reached");
}
// Wait for the backoff period
console.warn(`Attempt ${attempts} failed. Retrying in ${delay} ms...`);
await new Promise((resolve) => setTimeout(resolve, delay));
// Exponential backoff
delay *= 2;
} else {
// If it's a different error, rethrow it
throw error;
}
}
}
}from workers import WorkerEntrypoint, Response
import asyncio
class Default(WorkerEntrypoint):
async def fetch(self, request):
key = "common-key"
parallel_writes_count = 20
async def attempt_write(i):
return await retry_with_backoff(
lambda: self.env.YOUR_KV_NAMESPACE.put(key, f"Write attempt #{i}"),
success_result={"attempt": i, "success": True},
)
results = await asyncio.gather(
*[attempt_write(i + 1) for i in range(parallel_writes_count)]
)
return Response.json(list(results))
async def retry_with_backoff(fn, success_result, max_attempts=5, initial_delay=1.0):
attempts = 0
delay = initial_delay
while attempts < max_attempts:
try:
await fn()
return success_result
except Exception as error:
if "KV PUT failed: 429 Too Many Requests" in str(error):
attempts += 1
if attempts >= max_attempts:
raise Exception("Max retry attempts reached")
print(f"Attempt {attempts} failed. Retrying in {delay}s...")
await asyncio.sleep(delay)
delay *= 2
else:
raise