跳转到内容
搜索文档

写入键值对

最后更新 查看 MarkdownAgent 设置

要创建新的键值对,或更新特定键的值,请在已绑定到 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 提供以下方法用于写入:

put() 方法

要创建新的键值对,或更新特定键的值,请在已绑定到 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 对都需要 keyvalue。整个请求大小必须小于 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_epochsecondsFromNow/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"})

对同一键的 KV 写入限制

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

其他访问 KV 的方法

你也可以使用 Wrangler 从命令行写入键值对,并通过 REST API 写入数据

这篇文档对您有帮助吗?