边缘缓存不是一个全球共享副本:缓存位置、回源与失效边界怎么理解
同一个网址在两个地区出现新旧版本,通常不能只用‘缓存没清干净’解释。本文从缓存键、边缘副本、鲜度验证、回源路径和清除范围拆解差异,并给出一套不破坏缓存架构的排查顺序。
同一张图片或同一份网页刚刚更新,香港用户已经看到新版,欧洲同事却仍看到旧版。很多团队会立刻下结论:CDN没有同步完成,或者某个节点坏了。这个判断跳过了最重要的几层事实。两个请求是否属于同一个缓存键?它们由哪个边缘位置处理?副本仍然新鲜,还是已经向源站验证?清除动作又是否覆盖了真实变体?
边缘网络遍布全球,不代表全球只有一份即时同步的缓存对象。更可靠的理解方式,是把一次命中拆成请求身份、保存位置、复用条件和失效动作。只有四层证据都对齐,跨地区的新旧差异才适合被称为异常。
相同网址,可能不是同一个缓存对象
HTTP缓存不会只凭人眼看到的网址决定对象。RFC 9111规定,缓存键至少包含请求方法和目标URI,Vary可以把指定请求头加入匹配。平台还可能配置查询参数处理、设备类型、国家地区、语言或其他自定义字段。两个浏览器地址栏完全相同,只要参与缓存键的请求头不同,就可能命中两个独立变体。
例如源站根据Accept-Language返回中文与英文版本,并在响应中声明Vary。香港浏览器携带zh-CN,欧洲测试工具携带en-US,两边保存的内容本来就不应该合并。若平台又把移动设备与桌面设备纳入自定义键,同一地区也可能出现多份副本。此时清除一个裸URL,并不必然覆盖所有组合。
排查的第一步因此不是反复刷新,而是固定请求条件。保存完整URL、查询参数、方法、Host、Accept-Encoding、语言、设备相关字段和测试时间。若平台能输出实际缓存键或调试信息,也应一并留存。先证明两次请求确实在比较同一个对象,后面的地区差异才有意义。
全球可用的接口,不等于副本全球复制
边缘缓存的保存范围由产品和调用方式定义。Cloudflare Workers官方文件给了一个很清楚的例子:Cache API可在全球使用,但写入的内容不会自动复制到原始资料中心以外。某个资料中心缓存了GET /users,另一个资料中心不会因此自动拥有相同对象,除非那里也建立了它。
这项说明针对Workers Cache API。Cloudflare Cache API的局部性不能外推为所有CDN的统一行为,也不能代表Cloudflare的每一种缓存方式。它的价值在于纠正一个常见直觉:服务名称含有“全球网络”,描述的是可服务的覆盖范围,不是所有节点共享同一块内存。
如果两个地区分别第一次访问更新后的资源,各自可能经历不同路径。一个边缘位置已有旧副本并判断仍可复用;另一个位置没有对象,直接取得源站新版。表面看像传播不一致,实际是两个独立副本处在不同生命周期。只有确认保存位置和创建时间,才知道问题发生在复制、鲜度还是回源。

旧副本不一定有故障,它可能仍被允许复用
缓存响应带有鲜度规则。RFC 9111允许新鲜响应在不联系源站的情况下直接复用;超过鲜度期限后,缓存可使用ETag或Last-Modified发起条件请求。源站若确认内容未变,缓存继续使用原有主体;若已变化,再取得新表示。
因此,“源站已经更新”与“边缘必须马上变化”之间,还隔着缓存指令。若max-age尚未结束,旧副本仍可能符合发布者给出的复用条件。若响应要求no-cache,它不是完全禁止保存,而是要求复用前完成验证。no-store才是禁止缓存保存响应的指令之一。把这些语义混在一起,会让团队误把正常TTL当成清除失败。
副本先按缓存键匹配,再按鲜度决定直接复用、条件验证或重新回源。排查时可比较Age、Date、ETag、Last-Modified与平台缓存状态。Age较大且持续命中,说明正在复用已有副本;出现验证请求或304,则表示缓存与源站正在确认表示;MISS只表示当前路径没有可用命中,不自动证明源站内容正确。
Cache API、fetch与分层缓存不是同一条路
即使请求经过同一个Worker,不同代码路径也会产生不同拓扑。Cloudflare文件区分Cache API与fetch:Cloudflare Cache API只操作当前资料中心。fetch可以与分层缓存交互。Cache API的put、match、delete针对处理请求的边缘位置;fetch则可能先访问下层缓存,再由上层缓存集中回源。
分层缓存会减少源站请求,但也多了一层状态需要解释。下层没有对象时,不代表马上到源站;上层可能仍保存可用副本。相反,手工写入本地Cache API的对象,也不会因为另一个地区调用match而自动出现。若只查看业务URL而不知道代码使用哪条路径,很容易把两套缓存当作一套。
检查Worker或边缘函数时,应记录资源由cache.match/cache.put管理,还是由fetch及平台缓存配置管理。若两者混用,还要确认键的构造是否一致、响应头是否被改写,以及清除工具实际作用于哪套对象。发现差异后,不要急着关闭所有缓存;先把写入与读取的路径对应起来,通常更快找到遗漏。
清除动作必须覆盖真实的键与范围
清除缓存也不是一句抽象命令。平台可以按全部内容、URL、标签、主机名或前缀清除,各自覆盖范围和成本不同。Cloudflare API特别提醒,自定义缓存键的按URL清除需要带上参与计算缓存键的请求头。若缓存键包含设备、地区或语言字段,只提交URL可能无法准确定位所有变体。
这也解释了为何某些测试“清过一次”仍看到旧内容:发出的清除请求可能只代表一个请求身份,实际访问却使用另一个身份。较稳妥的做法,是从配置中列出所有键材料,检查清除API的请求是否按平台格式完整携带,而不是靠猜测多按几次按钮。
清除后的响应状态也要结合拓扑解读。Cloudflare按主机名清除的文件说明,后续请求通常是MISS;启用分层缓存时也可能显示EXPIRED,因为下层与上层会参与重新验证。看到EXPIRED不能单独证明清除动作失败,仍要比较内容、验证器与上层回源结果。
用一组可复现证据定位差异
第一次测试先固定对象。使用相同方法、URL、查询参数和关键请求头,从两个地区在接近的时间发出请求。不要一边用浏览器,一边用会自动增加不同压缩、语言或设备字段的监测器。必要时用同一条curl命令,并记录实际出口地区。
第二次测试记录响应。至少保存状态码、Date、Age、Cache-Control、ETag、Last-Modified、Vary和平台缓存状态。若响应内容很大,可计算主体摘要,避免只凭页面视觉判断。摘要相同而缓存状态不同,代表内容一致但路径不同;摘要不同且ETag不同,才说明两地正在使用不同表示。
第三次测试查写入方式与回源层级。确认资源来自普通CDN缓存、Worker Cache API、fetch分层缓存,还是应用自己的存储。再核对源站是否按地区或请求头返回不同内容。源站本身若有地理化响应,边缘差异可能忠实反映源站,而非缓存污染。
第四次测试才执行最小范围清除。清除前记录旧响应,清除后以完全相同条件复测,并保留清除请求的目标、时间和返回结果。固定请求条件并记录Age、ETag、Last-Modified和缓存状态,再核对写入方式与清除范围。这样才能判断变化来自清除、自然过期、条件验证,还是换了另一个缓存键。
哪些结论不能从一次地区差异推出
一个地区旧、一个地区新,不能证明CDN全球传播失败;可能只是副本独立、鲜度不同或请求键不同。清除后出现MISS,也不能保证下一步一定取得预期版本,因为源站、上层缓存和应用逻辑仍可能返回旧内容。缓存命中更不等于内容最新,它只表示当前规则允许使用某个已保存响应。
同样,关闭缓存通常不是一致性设计。它会增加源站负载与延迟,却不解决源站地区化、发布顺序、对象版本号或应用存储复制的问题。对需要强一致的关键数据,应使用明确的版本化URL、发布原子性或适合的状态系统,而不是把静态内容缓存当成同步数据库。
边缘缓存的可靠性来自可解释的规则。先确认请求是不是同一个键,再确认副本在哪里、何时创建、为何仍可复用,最后验证清除覆盖了哪些对象。完成这条证据链后,跨地区差异不再是一句模糊的“节点没同步”,而会落到具体且可修正的配置、代码路径或发布边界上。
资料来源
- RFC Editor:《RFC 9111: HTTP Caching》,发布或更新于 2022-06-01
- Cloudflare Developers:《Cache · Cloudflare Workers docs》,发布或更新于 2026-07-06
- Cloudflare Developers:《How the Cache works · Cloudflare Workers docs》,发布或更新于 2026-07-06
- Cloudflare API:《Purge Cached Content》,发布或更新于 2026-07-06
- Cloudflare Developers:《Purge cache by hostname》,发布或更新于 2026-04-16