先做一件事:把同一个URL分别从源站、CDN边缘节点和你所在地区的公共DNS解析路径各取一次响应,比较HTML里的<link rel="canonical">是否一致。如果三处不一致,问题几乎一定在缓存层而不是页面模板;如果三处一致但搜索引擎抓取到的版本不同,则要转向抓取链路和缓存键设计。这个动作不需要后台权限,用命令行或浏览器隐私窗口就能完成,结果直接决定下一步是找运维还是改代码。
多层缓存通常指浏览器缓存、CDN边缘缓存、反向代理缓存和应用层对象缓存。每一层都可能保存旧版HTML,导致同一个URL在不同请求下返回不同canonical。定位顺序建议从离你最近的层往外推:
?cachebust=123。若带参数时canonical正确、不带参数时错误,说明CDN或反向代理按无参数URL缓存了旧对象。curl -I看响应头中的Age、X-Cache、Cache-Control。若Age很大且canonical是旧值,基本可以锁定边缘节点。这三步能区分“模板输出错”和“缓存返回旧对象”。前者改模板即可,后者需要处理缓存键或主动刷新,动作完全不同。
多层缓存返回不同版本,最常见的原因是各层使用了不同的缓存键。CDN可能按URL+设备类型缓存,反向代理可能按URL+语言缓存,应用层可能按URL+登录状态缓存。当canonical标签依赖这些维度时,就会出现同一URL多版本。
可以做一个假设性比较:假设某页面模板根据Accept-Language输出不同canonical,CDN又按URL缓存而不区分语言头。第一个请求是中文,CDN缓存了中文版canonical;第二个请求是英文,CDN直接返回中文版HTML,英文用户看到的中文canonical就与页面语言不符。这个例子里,问题不在模板,而在缓存键没有包含Vary: Accept-Language。
实际动作是检查响应头是否有Vary字段,以及它与模板输出维度的对应关系。如果模板按某个请求头变化,而缓存层没有声明该头为缓存键的一部分,就应该让运维或开发补齐Vary或改用独立URL。这个动作的结果是:补齐后各层返回的canonical应趋于一致;若仍不一致,说明还有一层缓存未覆盖,需要继续往上游查。
没有CDN后台、没有服务器权限时,仍然可以做几件事来缩小范围:
Cache-Control、Age、ETag。这些字段能说明对象是否被缓存以及缓存了多久。这些动作不能证明缓存层是唯一原因,也不能推出搜索引擎一定抓到了错误版本。它们只能说明:在你观察的路径上,同一URL确实返回了不同canonical。要判断影响,还需要看抓取日志或索引状态,而这通常需要额外权限。
整理一份最小记录,包含:URL、请求时间、请求路径(是否带参数、是否带Cookie、Accept-Language值)、响应头关键字段、canonical实际值。把这份记录交给运维或开发时,直接说明你观察到哪两层返回不同,以及你怀疑的缓存键维度。
如果确认是缓存键问题,处理方向有两个:一是让缓存键包含模板依赖的维度,二是让canonical不随这些维度变化,改为固定指向规范URL。前者改动在缓存配置,后者改动在模板。选择哪个取决于业务是否需要按语言或设备输出不同canonical;如果不需要,固定canonical更简单,也更容易让各层缓存收敛到同一版本。
完成修改后,重复第一步的三处取样。只有源站、边缘节点和你的请求路径都返回同一canonical,才能说明一致性问题在观察范围内已消除。之后是否被正确抓取和索引,仍取决于搜索引擎的重新抓取,不能由本次修改直接保证。