怎么用 Cloudflare KV 做读穿缓存?

读穿缓存的流程只有三步:读 KV,没有就回源,回源的结果写回 KV。写起来几行,但热点 key 一过期,好几个位置会同时回源、同时写同一个 key,而 KV 同一个 key 每秒只允许写 1 次。

做法是:写回时设 expirationTtl,写回失败直接吞掉并返回已经拿到的数据,回源发现不存在时不要长期缓存空结果。

这三件事分别对应 KV 的三个特性:每秒 1 次的同 key 写入限制、不存在的 key 也会被缓存、值的大小上限。下面一个个说。KV 的一致性模型是它们共同的背景,需要的话先看 Cloudflare KV 能当数据库用吗?。

读穿缓存的基本流程是什么?

Worker 先读 KV,没有再回源(比如 D1 或上游 API),再写回,写回时设 expirationTtl。读写的 API 就是下面这两行的样子:

await env.CONFIG.put("flags:v1", JSON.stringify({ beta: true }), {
  expirationTtl: 3600,
});

const flags = await env.CONFIG.get<{ beta: boolean }>("flags:v1", {
  type: "json",
  cacheTtl: 300,
});

两个 TTL 别混:expirationTtl 在写入时设,决定这个 key 什么时候过期,最小 60 秒;cacheTtl 在读取时设,决定结果在读取位置缓存多久,最小 30 秒,默认 60 秒。两个下限我在本地都验证过,低于下限会返回 400。

热点 key 过期的瞬间会发生什么?

这就是缓存击穿。热点 key 过期的瞬间,多个位置同时回源,并同时写同一个 key,会撞上每秒 1 次的写入限制,超出的写入返回 429。

处理办法是:回源后的写入失败应当吞掉。你已经从源头拿到了数据,直接返回它就行,不要让缓存写失败变成请求失败。

要注意,这个 429 我没能在本地复现,连续写同一个 key,本地全部成功。所以别指望本地测试替你发现这个问题,吞掉写失败这一步要在代码里主动写上。

回源发现数据不存在,要缓存吗?

不要长期缓存。KV 会缓存不存在的 key,这叫负缓存。如果你把一个“空值”写进去并设了很长的 TTL,资源创建之后很久都读不到。

两个选择:给空值一个很短的 TTL,或者干脆不缓存空结果。

还有一点:读取不存在的 key 同样计费,所以大量请求打在不存在的 key 上,每次都会回源又每次都要付读的钱。具体的计费规则见 Cloudflare KV 的限额和计费是怎么算的?。

缓存的值很大怎么办?

单个值上限是 25 MiB,单次响应也受这个限制。大值请使用 stream 或 arrayBuffer 读取。我在本地试过,超过 25 MiB 的 value 写入会返回 413。

如果你缓存的是大文件本身,而不是它的一份计算结果,那更应该看 R2。

读穿缓存之后,数据以谁为准?

以回源的那一侧为准。我自己的做法是:先把数据写进权威存储(D1 或 Durable Objects),再投影到 KV。KV 读到的可能是旧的,旧到什么程度见 Cloudflare KV 能当数据库用吗?。任何“没有 KV 就不正确”的逻辑,都应该重新设计。

限额与行为以 2026-10 官方文档为准,会变化。负缓存、cacheTtl 默认值、同 key 每秒 1 次写入并返回 429、读不存在的 key 计费,都是官方文档的说法,我没有在线上验证。value 超过 25 MiB 返回 413、两个 TTL 下限返回 400,我在本地 wrangler dev 上跑过;本地没有复现 429,也看不到跨地区的缓存传播,回源并发击穿的情形没有实际测过。