从一次真实痛点说起
故障定位思路
关于边缘缓存,最值得先弄清楚的是配置边界和排错顺序。上个月我的个人博客突然涌入大量流量,某篇技术文章在Hacker News上火了。原本托管在廉价VPS上的WordPress瞬间飙到CPU 100%,数据库连接池爆满,页面加载时间从200ms直接跳到8秒。更糟糕的是,VPS带宽只有5Mbps,大量请求被排队丢弃。我紧急开启了Cloudflare CDN代理静态资源,但动态页面仍然需要回源——每次请求都要经过PHP+MySQL的完整渲染链,瓶颈无法消除。
就在我准备升级服务器配置时,突然想到:为什么不把整页响应缓存到Cloudflare的边缘节点?但传统CDN只能缓存静态文件,对动态页面无能为力。于是我把目光投向了Cloudflare Workers——这个能运行在350+全球节点上的Serverless平台,配合其键值存储Workers KV,完全可以实现自定义的边缘缓存层。
经过一下午的折腾,我搭建了一套毫秒级边缘缓存系统。效果惊人:全球平均响应时间从800ms降到15ms,源站带宽消耗减少了95%,而且完全不需要改动原有后端代码。这篇文章我会把完整方案、代码以及踩过的坑全部拆解给你。
关联教程:此处可内链到“边缘缓存部署与验证”内容。
边缘缓存为什么需要Workers+KV
传统CDN缓存的局限性
Cloudflare CDN默认会根据响应头中的Cache-Control来决定缓存时长。但动态页面通常设置no-cache或private,CDN无法缓存。即使强行缓存,也存在两个问题:一是缓存粒度粗,不能按用户身份或参数做差异化缓存;二是缓存失效不够灵活,无法主动清除特定URL。
Workers作为智能缓存层
Cloudflare Workers运行在CDN节点上,可以拦截所有请求,自行决定是否命中缓存。它类似于一个反向代理层,但拥有完整的JavaScript运行环境。我们可以用Workers在边缘节点上执行复杂的缓存逻辑:读取请求头、查询KV缓存、回源获取新内容、更新缓存。
KV存储:全球分布的缓存池
Workers KV是Cloudflare提供的全球分布式键值存储,数据在写入后几秒内就能传播到所有节点。虽然它不适合高频写入(有写入一致性延迟),但对于缓存场景——读多写少、允许最终一致性——简直是绝配。KV存储每次读取通常在5-15ms内完成,配合Worker的本地内存缓存,能达到亚毫秒级命中。
延伸阅读:此处可内链到“边缘缓存配置案例”相关文章。
想继续深入:此处可内链到“边缘缓存优化清单”文章。
架构设计:分层缓存+按需回源
故障定位思路
我设计的边缘缓存系统分为三层:
- 第一层:Worker本地内存 —— 每个Worker实例在内存中维护一个Map,缓存最近访问的页面。因为Worker实例会在节点上复用,内存缓存命中率很高,延迟<1ms。
- 第二层:KV存储 —— 把渲染后的完整HTML存入KV,Key使用请求URL的哈希值。KV读取延迟约5-15ms,但全球共享。
- 第三层:回源源站 —— 当KV也未命中时,Worker回源请求后端,获取最新内容,并异步写入KV和内存缓存。
缓存过期策略采用TTL控制:对博客文章设置1小时过期,首页和分类页设置10分钟。同时支持手动清理:通过Webhook触发删除特定KV键。
完整代码实现
故障定位思路
下面是我实际部署的Worker脚本核心逻辑,已去掉个人配置项:
// 内存缓存,每个Worker实例独享
const memoryCache = new Map();
const MEMORY_TTL = 60 * 1000; // 1分钟
async function handleRequest(request) {
const url = new URL(request.url);
const cacheKey = url.href;
// 1. 检查内存缓存
const memCached = memoryCache.get(cacheKey);
if (memCached && Date.now() - memCached.time { headers[key] = value; });
// 4. 异步写入KV和内存缓存
const ttl = determineTTL(url);
const cacheData = JSON.stringify({ body, headers });
KV_NAMESPACE.put(cacheKey, cacheData, { expirationTtl: ttl }).catch(() => {});
memoryCache.set(cacheKey, { body, headers, time: Date.now() });
return new Response(body, {
headers: { 'x-edge-cache': 'miss', ...headers }
});
}
function determineTTL(url) {
if (url.pathname.startsWith('/blog/')) return 3600;
return 600;
}
export default { fetch: handleRequest };
注意:上面代码省略了错误处理、缓存清理接口和URL规范化逻辑。实际生产环境中一定要处理大内容(KV有25MB限制)以及压缩传输。
性能测试:不测不知道,一测吓一跳
测试环境
- 源站:东京DigitalOcean 1核1G VPS,WordPress+PHP+MySQL
- Workers部署在全球节点,KV存储位于美国
- 测试工具:curl并记录耗时,同时使用WebPageTest从全球多节点测试
结果对比
回源首次请求:TLB连接 + SSL握手 + 后端渲染 + 网络传输 ≈ 1.2s(从美国东海岸请求东京源站)
KV命中:Worker判断 + KV读取 + 响应 ≈ 18ms
内存命中:≈ 2ms
在连续请求下,内存命中率约70%,KV命中率约25%,回源率仅5%。全球平均加载时间从800ms降至15ms,性能提升超过50倍。
避坑指南与优化技巧
1. KV写入延迟问题
KV写入不是立即生效的,需要等待几秒到十几秒的传播。如果用同一个Worker在写入后立即读取,可能拿到旧值。解决方案:先更新本地内存,再异步写入KV;同时设置合理的TTL,让不一致窗口尽可能短。
2. 内存缓存膨胀
每个Worker实例的内存有限(免费版128MB),如果缓存大量页面可能导致OOM。我设置了最大缓存条目数(比如500条),超出后淘汰最旧的。另外利用WeakRef?目前不成熟,简单用Map+定时清理即可。
3. 缓存键设计
对于带查询参数的URL,需要排除无关参数(如utm_source)。我实现了URL规范化函数,只保留必要参数并排序。
4. 缓存的主动清理
当发布新文章或修改内容时,需要立即清除对应页面的缓存。我配置了WordPress的save_post钩子,发布文章时向Workers发送一个清理请求,删除缓存键。
边缘缓存:适用场景与扩展思考
这套方案特别适合以下场景:
- 博客、文档站、静态站点生成器(如Hugo、Next.js SSG)的动态功能(评论、搜索)
- API接口的响应缓存(如天气、汇率等更新不频繁的数据)
- 电商页面(产品详情、分类页),适合库存变化不剧烈的商品
如果业务对实时性要求极高(如交易系统),则不适用。另外KV存储有写入频率限制(免费版每分钟1000次写入),高并发写入需要谨慎。
未来可以扩展:结合Durable Objects实现强一致缓存、配合R2存储缓存大文件、使用Service Bindings让多个Worker共享缓存逻辑。
总结
Cloudflare Workers+KV的组合把边缘计算和全球缓存的能力完全交到了开发者手中。这套毫秒级边缘缓存不仅解决了我的燃眉之急,还让我省下了每月几十美元的服务器升级费用。对于追求极致性能的站长和开发者来说,这种DIY缓存层比任何商业CDN产品都更灵活、更高效。
如果你也在被源站性能问题困扰,不妨花一个下午试试这个方案。相信我,当你在Chrome DevTools里看到响应时间只有个位数毫秒时,你也会感叹一句:太香了。真正做好边缘缓存,靠的不是参数堆砌,而是持续验证。
延伸阅读
