Vary响应头实测:说变的3个,真变的17个
本文目录
- 同一个地址,为什么会给出不一样的东西?
- 请求里带着的那几行字
- 缓存必须知道这件事
- 缓存投毒是怎么发生的
- 为什么这条声明特别容易漏
- 协商和跳转是两条不同的路
- 为什么内容协商用的人越来越少
- 说了随什么变的有几个,真的在变的有几个?
- 声明侧:92.1% 的站发了Vary,但内容高度集中
- 写多了会怎样
- 实测侧:17个站确实随语言变
- 变化的具体形态
- fiskars那个芬兰语版说明了什么
- 那3个声明了随语言变的站
- 从64收到17,中间那47个是怎么回事?
- 第一版判据错在哪
- 换成什么判据
- 为什么第一版数字看着更像真的
- 这件事本身的教训
- 虚高的数字为什么总是往同一个方向偏
- 压缩这一层的默认协商
- 带上压缩偏好时给什么
- 不带压缩偏好时给什么
- 17个站的编码变了却没声明
- 一次真实的排查:慢,但慢得没道理
- 一个都没有出现的编码
- 压缩比这个数还能用来做什么
- 不写Cache-Control的那30个站,缓存怎么办?
- 21.6% 的站没有这个头
- 写了的那109个站写了什么
- ETag有一半的站在发,它派什么用场
- 缓存命中率的实际情况
- 首页不缓存,不等于整站都不该缓存
- Age头能告诉你什么
- 首次请求就写下的778条Cookie,是谁写的?
- 85.6% 的站在第一次请求就发Cookie
- 这些Cookie是干什么的
- 为什么这份名单里几乎没有自己写的东西
- Domain属性没写的有537条
- 安全属性的覆盖率说明了什么
- 第三方域的Cookie只有4个站
- 七条Cookie意味着每个请求多带多少字节
- 同意条弹出之前就已经写好了
- 一次响应里36个头字段,你认识几个?
- 中位36条,最多235条
- 覆盖率最高的那一批
- 头字段数量的两端分别是什么站
- 边缘层是谁
- 64% 的站宣告了下一代协议,这次一次都没用上
- 272种字段名里,有多少是给人看的
- 安全头那一组的覆盖率为什么高
- 这些不成文的缺省,到底是谁定的?
- 三个来源,各管一段
- 三层各自的可预测性
- 为什么第三层最容易漏
- 怎么判断一个行为是哪一层来的
- 平台层的行为会变,而且不通知你
- 这些缺省该怎么测出来?
- 测试矩阵怎么搭
- 比对什么才算数
- 然后和Vary对一遍
- 把它做成一次可重复的检查
- 用什么工具
- 什么时候跑
- 结果怎么读才不误判
- 一份可以照着跑的实测清单
- 五分钟版
- 五分钟版能查出什么,查不出什么
- 半天版
- 顺手能查出来的另外三件事
- 做完之后写在哪
- 这份清单该由谁维护
- 测出来之后,该改哪一层?
- 能在源站改就别在边缘改
- 如果非要在边缘改
- 修复的优先级怎么排
- 把语言分发从内容协商换成显式地址
- 改完怎么验证
- 一个不改代码的过渡方案
- 搜索引擎拿到的是哪一版
- 回到那条一般规律
- 把这套东西交给别人时该说什么
- 常见问题解答
- Vary头到底该写什么?
- 怎么快速测出自己的站随什么变?
- 比对两次响应时,用整页哈希可靠吗?
- 首页不写Cache-Control会怎么样?
- 为什么很多站的首页干脆写no-store?
- 首次请求就被写入七八条Cookie正常吗?
- Cookie不写Domain属性有什么影响?
- 不压缩的响应比压缩的大多少?
- 权威参考资料
摘要:给139个品牌站的首页各发4次请求,地址一个字不改,只换请求头。结果是128个站发了Vary头声明自己随什么变,其中写明随语言变的只有3个;而实测真的随语言变的有17个,差5.7倍。压缩那一层同样:17个站的编码随请求变,Vary里也没写。这些行为没有任何一份文档记着,只能靠发两次不同的请求把它试出来。
先描述一个很容易被忽略的场景。
你的站上线了,首页地址只有一个。你在浏览器里打开它,看到的是英文版,价格是美元。同一时刻,另一个人在别的地方打开同一个地址,看到的是德语版,价格是欧元。
中间没有跳转,地址栏里的字符完全一样。是服务器根据请求里附带的信息,当场决定给哪一版。
这件事本身没问题,多语言站几乎都这么做。问题在于:你的服务器在按什么条件变,没有任何一份文档写着。它不在代码注释里,不在部署文档里,也不在你交给同事的那份说明里。它只存在于服务器的实际行为中,要知道答案只有一个办法——发两次不同的请求,看看拿回来的东西一不一样。
更麻烦的是中间还隔着缓存。缓存要决定一份副本能不能给下一个人用,靠的不是观察行为,而是读一个叫Vary的响应头。这个头是站点自己声明的:我随哪几个请求头变。声明写对了,缓存分得清;写漏了,缓存会把德语版发给要英文的人。
所以这里有一对可以直接比对的东西:一边是站点声明自己随什么变,一边是它实际随什么变。这次把211个海外品牌站的首页各请求四次做了这个比对,能正常返回正文的139个站,下面全是从这139份响应里数出来的。
同一个地址,为什么会给出不一样的东西?
先把机制讲清楚,不然后面的数字没法读。
这几个头字段各管什么,X-Robots、缓存与Vary的分工有一份逐项拆解。
请求里带着的那几行字
浏览器请求一个地址时,除了地址本身,还会附上一批说明自己情况的头字段。常用的有这么几个:Accept-Encoding 说自己能解哪几种压缩,Accept-Language 说自己偏好哪种语言,User-Agent 说自己是什么客户端,Cookie 带着之前存下的状态。
这些字段在日志里就是识别依据,八类UA的识别与流量归因可以直接照抄。
服务端读这些字段做判断很常见,五种识别爬虫的写法就是拿其中一个字段做的。
服务器可以读这些字段,然后决定返回什么。返回压缩过的还是没压缩的、返回英文版还是德语版、返回桌面版还是移动版、返回登录态还是游客态——都是同一个地址下的不同答案。
这套机制叫内容协商,它是HTTP从一开始就有的设计。它的好处是地址干净,坏处是同一个地址不再对应唯一的内容。
缓存必须知道这件事
缓存的工作是把响应存下来,下次有人请求同一个地址就直接给。如果同一个地址会给出不同答案,缓存就必须知道该按什么把这些答案区分开。
参数也会让同一个页面变成多个地址,给内链加追踪参数为什么伤SEO是同一类问题。
缓存头这一套怎么配才不出事,让回头客秒开又不犯改了不更新的配置讲得比较全。
这就是 Vary 的用途,它的取值会直接参与缓存键的计算。服务器在响应里写一句 Vary: Accept-Encoding,意思是这份内容随压缩偏好变,请按这个字段分开缓存。写 Vary: Accept-Encoding, Accept-Language,就是按两个字段的组合分开存。
写漏了会怎样?缓存会以为这个地址只有一个版本,把第一个人拿到的那份存下来,发给后面所有人。第一个访客碰巧是德国来的,后面所有人都会拿到德语页。这类事故在业内有个专门的说法,叫缓存投毒。
缓存投毒是怎么发生的
把这个后果讲具体一点,因为它是本文所有讨论的现实动机。
发错语言版本的代价不只是体验,五个翻译陷阱与人审六步说的是本地化的分量。
边缘缓存的规则怎么设计,回源率优化的八维决策树给了可照抄的判断路径。
假设你的站按语言返回不同内容,但没有声明。边缘缓存收到第一个请求,来自德国,返回德语页,缓存把它按地址存下来。此后一小时内,所有请求这个地址的人——不管来自哪里、带什么语言偏好——拿到的都是那份德语页。
这个故障有三个特别难查的特征。第一,它是概率性的,取决于缓存过期后第一个请求碰巧来自哪里;第二,它对你自己不可见,你在办公室刷新看到的多半是对的,因为你的请求可能命中另一个边缘节点;第三,它不产生任何错误日志,服务器认为自己正确响应了每一个请求。
用户那边的表现是“网站有时候是德语的”,这句话传到技术这边通常会被当成用户搞错了。等到能稳定复现的时候,往往已经过了很久。
把这个故事记住,后面那些覆盖率数字读起来会不一样:2.2% 这个数不是一个体检指标,它对应的是一类查起来极其费劲的线上故障。
为什么这条声明特别容易漏
因为它和产生变化的那段代码通常不在一起。
本地开发看不见的问题最难防,信息词流量过大的六大危害也是上线后才显形。
改动与后果隔得远时要靠记录补上,把变更做成日志的十三类信号是这么做的。
决定返回哪种语言的逻辑,可能写在应用层的中间件里,也可能写在边缘节点的脚本里。而 Vary 这个头,多数情况下是服务器或者CDN自动加的——加的是它自己知道的那部分。
比如压缩这件事是服务器自己做的,所以它会自动补上 Vary: Accept-Encoding。而按语言分发是你的业务代码做的,服务器不知道,也就不会替你补。会自动补的那部分不用你操心,需要你操心的那部分没有任何提示。这就是接下来所有数字的成因。
协商和跳转是两条不同的路
顺便把两种做法的区别说清楚,因为后面会反复用到。
选定之后还要配套一条生产线,从翻译外包到原生再创作的工程体系是那条线怎么搭。
多语言站选哪条路影响很大,多语言站的避坑清单把两种做法的代价列清楚了。
一种是内容协商:地址不变,服务器根据请求头当场决定返回哪一版。地址栏里始终是那一个地址,用户看不出发生过选择。
另一种是跳转:服务器看一眼请求头,直接回一个跳转,把人送到 /zh-cn/ 这样的独立地址上。用户能从地址栏看出来自己被送到了哪个版本。
从缓存的角度看,这两条路的性质完全不同。跳转把一个变化的地址拆成了几个固定的地址,每个地址内容唯一,缓存不用分版本;协商把变化留在了同一个地址里,缓存必须靠 Vary 才分得清。
从搜索的角度看,差别更大:独立地址可以被分别收录、分别排名、分别做外链;协商出来的多个版本共用一个地址,搜索引擎只会收录它拿到的那一版。
为什么内容协商用的人越来越少
这套机制写进标准很早,但真正大规模用的场景其实只剩压缩这一项。原因就在上一段:地址唯一在搜索这件事上是负资产,多语言站基本都改用独立地址了。
跨市场的问题正在换形态,为什么hreflang挡不住跨市场知识污染是新出现的那一类。
判断搬到边缘之后玩法就变了,在CDN边缘改SEO的原理与落地形态是这一层的入门。
不过它并没有消失,而是换了个位置继续存在——现在做协商的常常不是源站,是边缘节点。边缘按访客所在地区决定给哪一版,这在技术上仍然是内容协商,只是判断依据从请求头换成了来源地址。
这个变化带来一个新问题:来源地址不是一个请求头,你没法用 Vary 声明它。各家CDN因此定义了自己的字段,比如把访客国家写成一个自定义头再放进 Vary。本文样本里有1个站这么做了,是全样本唯一一个把地区分发正确声明出来的。
说了随什么变的有几个,真的在变的有几个?
直接看结果。
服务器这一层要核对的项不止这一个,二十项服务器配置清单可以对照着过。
声明侧:92.1% 的站发了Vary,但内容高度集中
139个站里有128个在首页响应里发了 Vary,覆盖率92.1%。乍看很健康。
体验信号也是一组被平台定义的默认,六项体验信号怎么优化与排序是那边的清单。
覆盖率高不等于做对了,资深团队的技术SEO为什么会失灵说的是同一类错觉。
但拆开看令牌就不是那么回事了。
| Vary里包含的字段 | 站数 | 占比 |
|---|---|---|
| Accept-Encoding | 120 | 86.3% |
| Cookie | 4 | 2.9% |
| Accept-Language | 3 | 2.2% |
| User-Agent | 3 | 2.2% |
| Origin | 2 | 1.4% |
压缩那一项独占鳌头,其余全是零头。这和上一节说的机制完全对得上:Accept-Encoding是服务器自动补的,其余得靠人写,而人几乎不写。
另外还有一批值得一提的令牌:6个站写了 RSC、Next-Router-State-Tree、Next-Router-Prefetch 这类框架自定义字段,那是前端框架自己加的;1个站写了 CloudFront-Viewer-Country,说明它按访客国家分发并且正确声明了——这是全样本里做得最规范的一个。
写多了会怎样
前面一直在说漏写的危害,写多了也有代价,只是方向相反。
切分维度多了就会稀释,交叉分类的五种优化方法是控制切分的做法。
切得太碎直接反映在首字节时间上,多层缓存怎么同时影响体验与抓取能看到这条链路。
Vary 里每多一个字段,缓存就要按这个字段的取值再切一层。写 Vary: User-Agent 是最经典的反面例子——用户代理字符串的取值几乎是无限的,每一个细微的版本号差异都会被当成一个新版本,结果是缓存里存了成千上万份几乎一样的副本,命中率趋近于零。
样本里写了这一项的有3个站。它们可能确实按设备返回不同内容,但即使如此,更好的做法也是把用户代理归一化成几档,或者干脆换成客户端提示那一套字段。
Vary: Cookie 同理,4个站在用。Cookie里通常带着会话标识,那是每人一份的,按它切缓存等于给每个访客单独存一份,和不缓存没有区别。
所以这一项的判断标准是:这个字段的取值空间有多大。取值只有几种(比如压缩算法、语言)的可以放心写;取值近乎无限的(用户代理、Cookie)要么归一化,要么别走缓存。
实测侧:17个站确实随语言变
接着看行为。测法是拿同一个地址发三次请求,第一次带英语偏好,第二次带中文偏好,第三次干脆不带这个字段,然后比对三次拿回来的内容。
语言之外货币也会跟着变,把跨市场价格排得像本地店是配套的处理。
同语言多地区最容易出这种事,同一种英语卖到几个市场怎么不打架是配套的处理。
按严格判据统计——只有跳转到了不同地址、页面语言标记不同、标题不同,或者正文体积差超过0.5%,才算真的变了——139个站里有17个(12.2%)确实随语言变,而它们的Vary里都没写Accept-Language。
把两个数放一起:声明随语言变的3个,实际随语言变的17个。而这17个和那3个还不是包含关系——那3个声明了的站里,有的在这次测试中没有表现出差异。
变化的具体形态
17个站里,变化分三类。
换一种语言用户搜的词也变了,跨文化语义与查询语言切换是这条线的完整流程。
地址结构变了影响的不只一处,目录层级的六招优化可以一起考虑。
跳转规则要和标注对得上,return tags对称与x-default实操是核对的依据。
第一类是跳到不同地址,有8个:aloyoga.com在带中文偏好时落到 /zh-hans-cn,不带偏好时落到根路径;suitsupply.com从 /en-cn/ 变成 /zh-cn/;rapha.cc从 /us/en 变成 /gb/en;ouraring.com反过来,从根路径跳去 /zh-CN。fiskars.com最有意思,不带语言偏好时它落到了 /fi-fi,也就是芬兰语版——那是这家公司总部所在地,是它内部的默认值。
第二类是地址没变但页面语言标记变了,有4个:canyon.com的 lang 从 en-cn 变成 zh-cn;casetify.com从 en 变成 zh-Hans-cn;mackweldon.com从 en-CN 变成 en-US。
第三类是地址和标记都没变,正文体积明显不同。delonghi.com差了59.36%,aliexpress.com差了51.96%,peakperformance.com差了8.31%。体积差一半以上,基本可以断定返回的是完全不同的一套内容。
fiskars那个芬兰语版说明了什么
单独说一下fiskars.com这个例子,因为它把缺省这件事演示得特别完整。
兜底语言这件事在异常页上更明显,撞上404那一刻本地化就管不到了是同一个机制。
默认值来自组织内部这件事很常见,本地化数据看着有的多半是借的是另一个形态。
带英语偏好请求时,它落在 /en-en;把语言偏好这个字段整个去掉之后,它落到了 /fi-fi,芬兰语版。
这家公司总部在芬兰。也就是说,当请求没有表达任何偏好时,系统给出的是组织内部的那个默认值,而不是覆盖面最广的英语版。
这个设计从公司内部视角看完全合理——不知道你是谁的时候,先按自己家的来。但从访客视角看,一个不带语言偏好的请求最可能来自脚本、爬虫或者简易客户端,而它们拿到的是芬兰语版。
类似的还有aloyoga.com、glossier.com、liquiddeath.com这三个:带中文偏好时落在带地区后缀的路径上,不带偏好时落到根路径。去掉一个字段就换一个版本,而这件事在任何一份对外文档里都查不到。
那3个声明了随语言变的站
公平起见也看看做对的那边。样本里有3个站在 Vary 里写了 Accept-Language,另有1个站用自定义字段声明了按访客国家分发。
跨语言对齐做对的站都是修出来的,实操对不上的五大根因是那些坑的清单。
做对的通常是修过的,五百个站实测排出来的优先级也是从事故里排出来的。
有意思的是,这3个站在本次测试中并没有表现出语言相关的差异——它们声明了会变,实测没变。
这不算错,反而是更保守的做法:声明的范围比实际变化的范围大,缓存会切得碎一点、命中率低一点,但绝不会串版本。宁可多声明也不要少声明,这个方向上的偏差代价是性能,另一个方向上的偏差代价是给错人。
把这四个站放在一起还能看出一件事:真正把这件事想明白的站,通常是那些做了多市场业务、并且在缓存上吃过亏的。正确的配置往往不是设计出来的,是修出来的。
从64收到17,中间那47个是怎么回事?
这一节讲测量本身,因为第一版尺子给出的数字是64,是最终数字的3.8倍。
口径没定死结论就会飘,从指标体系到异常诊断是先把口径立住的做法。
第一版判据错在哪
最初的判法很直觉:把两次响应的正文做个指纹,指纹不同就算变了。为了避开页面里的随机串,先用正则把长十六进制串、纯数字时间戳、nonce 属性都替换掉再算。
页面内容是否稳定影响一切测量,四种渲染模式下能读到多少是前置问题。
换台设备结果就变的问题同理,排名监测对不上的六大原因也是测量方法本身出的错。
结果是64个站(46%)指纹不同。这个数字当时看着很爽——将近一半的站在静默变体。
然后翻明细,发现一大批长这样:assos.com 正文不同(558833 -> 558833)。指纹不同,字节数一个不差。
字节数完全相同而内容不同,唯一合理的解释是页面里有等长的随机值——某个会话标识、某个请求追踪号、某个防重放令牌,长度固定,每次生成都不一样。那一版正则只清掉了几种常见形态,剩下的没清干净。
换成什么判据
改成只认四类硬证据:跳转后的最终地址不同、html 标签上的 lang 属性不同、页面标题不同,或者正文体积差超过0.5%。
标题这类字段稳定又有语义,H1与页面标题的关系说明了它为什么可靠。
指标要有唯一来源才不会各说各话,一套靠得住的五大指标给了治理框架。
这四类都有一个共同点:它们不可能由随机串造成。随机串不会改地址,不会改语言标记,不会改标题,也不会让体积差出半个百分点以上。
换判据之后数字从64掉到17。收缩得很厉害,但17这个数是站得住的——每一个都能点开具体差异看到实证。
为什么第一版数字看着更像真的
补一句当时的心理过程,因为它比技术细节更值得留意。
想验证一个因果就得设计实验,哪条外链真撬动排名的六步实验设计是可迁移的方法。
符合预期的数字最不该轻信,核心指标解析的四个常见错误都是这么来的。
46% 这个数出来的时候,保哥第一反应是“果然有这么多”。它和事前的预期完全吻合——大家都不写Vary,所以静默变体应该很普遍。一个符合预期的数字,是最不容易被检查的数字。
促使去翻明细的其实是另一件事:本来想在文里举几个具体例子,需要点名几个站。一翻就看到了那批字节数完全相同的记录。
也就是说,发现错误靠的不是审慎,是恰好需要举例子。如果这篇文章只打算给一个百分比,那个错误数字会原封不动地留下来。
这件事之后加了一条习惯:任何一个要写进结论的比例,都必须能点出三个具体样本,并说清楚它们各自为什么被算进去。举例子这个动作本身就是校验。
这件事本身的教训
这已经是做这类实测时第二次栽在尺子上了。上一次是特征匹配串写得太短造成误判,这一次是噪声没清干净造成虚高。
对账这件事要有固定动作,埋点归因看板的七个动作点是一份可执行的清单。
该退的指标不退就会继续误导,九个该淘汰的SEO指标是同一种清理。
共同点是:两次都是尺子偏向于报告更多的问题,而更多的问题看起来更像成果。如果不去翻明细,64这个数会被直接写进结论,而且没人会质疑——毕竟它符合大家对这类问题的预期。
所以做完任何一轮自动统计,都要抽样翻原始记录。判断标准很简单:随便抽三条,能不能一条条讲清楚它为什么被算进来。讲不清楚的,说明尺子有问题,不是样本有问题。
顺带说一句,宽判据那64个也不是全无价值。它说明将近一半的站每次返回的字节都不完全一样,也就是这些页面在响应层面不具备可重复性——这对想做逐字节比对的监控是个前提条件,得先把噪声字段排除掉。
虚高的数字为什么总是往同一个方向偏
再深挖一层:为什么两次栽跟头,尺子都是偏向报告更多问题,而不是更少?
口径切错方向结论就反了,品牌词与非品牌词为什么要分开算是同一类切分问题。
报表被污染也是单向偏的,机器流量怎么揪出来再拦掉是把噪声先剔掉的做法。
原因在于这类检测的构造方式。你写的是一个“找不同”的程序,它的默认输出是“有差异”,只有当两边完全一致时才输出“无差异”。任何一个没考虑到的干扰因素,都会让结果落在“有差异”那一边。误差不是随机的,它有方向。
反过来,如果程序写成“找相同”,比如只在满足某几个具体条件时才判定有问题,那漏报会多、误报会少。两种写法各有代价,关键是知道自己用的是哪一种,误差往哪边偏。
更麻烦的是心理层面。报告更多问题的结果看起来更像成果,而看起来像成果的东西,人是不会主动去质疑的。这一点比技术上的疏忽更值得警惕,因为它会让你在有机会发现错误时选择不去看。
可用的对策只有一个,而且很土:每轮统计跑完,从结果里随机抽三条,逐条讲清楚它为什么被算进来。讲不清楚就是尺子的问题。这一步花不了十分钟,本文两次纠错都是靠它。
压缩这一层的默认协商
语言那条线讲完,看另一条更基础的:压缩。
体积直接决定加载表现,页面速度到底怎么影响排名给了优先级排序。
带上压缩偏好时给什么
正常浏览器请求都会带 Accept-Encoding,声明自己能解gzip、br之类。这次的第一个变体就是这么发的,结果是:
体积只是性能的一环,六层架构把加载时间压下来的实操是完整路径。
压缩这件事还能在应用层再做一层,免插件压缩与压缩算法叠加是具体写法。
| 返回的编码 | 站数 | 占比 |
|---|---|---|
| br | 97 | 69.8% |
| gzip | 40 | 28.8% |
| 不压缩 | 2 | 1.4% |
br是主流,接近七成。这个比例比几年前高很多,主要是各家CDN把它做成了默认。又一个不用你操心的缺省,而且是往好的方向缺省的。
不带压缩偏好时给什么
第二个变体把这个头去掉了。结果非常整齐:137个站全部返回不压缩的内容,没有一个例外。
移动端对体积最敏感,十大致命错误与修复里有几条正是这个。
弱网下体积的影响会被放大,低端机与弱网的移动端性能优化讲的就是这类场景。
这是标准要求的行为——客户端没声明能解压缩,服务器就不该压。整齐到没有例外,说明这一层的实现是各家服务器软件自带的,没人动过。
有意思的是体积。不压缩与压缩的体积比,中位数是6.44倍。也就是说一个压缩后200 KB的首页,原始体积在1.3 MB上下。
这个倍数值得记一下,因为它是判断某些异常的参照。如果你在日志里看到某个客户端拉走的字节数是常规的六七倍,多半不是它在攻击你,而是它没带压缩偏好——一些老旧的抓取脚本和简易的监控探针就是这样。
17个站的编码变了却没声明
把两个变体的 Content-Encoding 一比,有17个站的编码确实随请求变,而它们的Vary里没有Accept-Encoding。名单里有aboutyou.com、temu.com、swarovski.com、philips.com、onepeloton.com这样量级不小的站。
多层处理是这类丢失的温床,六层缓存与边缘路由的实战配置能看清各层职责。
这类问题要从网络层往下查,从DNS、线路到CDN的排障顺序是可用的路径。
这一类的风险比语言那一类更直接:如果中间有一层缓存把不压缩的版本存了下来,后面所有带压缩偏好的请求都会拿到未压缩的内容,体积直接涨六倍多。页面看着一切正常,只是慢了,而且慢得没有道理。
为什么这17个站会漏?边缘配置改错一处就足以让实际交付和预期对不上,大概率是响应经过了不止一层:源站发出时带了Vary,某一层代理或者边缘脚本重写了响应头,把它丢了。这种丢失在任何一层单独看都是正常的,只有把首尾两端接起来看才发现少了东西。
一次真实的排查:慢,但慢得没道理
这类问题在实际项目里长什么样,举个保哥经手过的例子。
查这类问题日志要留得住,日志轮转与检索不爆盘的配置是前置工作。
这类问题通常卡在前端与运维之间,前端工程师的七个协作动作点能减少扯皮。
一个做家电配件的独立站找过来,说欧洲用户反馈页面加载慢,但监控里的各项指标都正常。第一轮查下来确实找不到毛病:服务器响应时间没问题,图片也压过,边缘节点覆盖也够。
后来是在浏览器的网络面板里注意到,主文档的传输体积和资源体积几乎一样大——也就是没压缩。而同一个页面在办公室打开是压缩过的。
顺着这条线查到边缘层:他们前段时间加了一段脚本给响应补安全头,那段脚本用的是整体替换响应头而不是追加,把源站发的 Vary 冲掉了。缓存于是不再区分压缩版本,某个节点存了一份未压缩的副本,此后就一直发这一份。
改动很小,把替换改成追加,加上一行把原来的 Vary 保留下来。但从用户反馈到定位原因中间隔了三周,因为所有常规监控指标都是正常的——服务器确实很快,只是发出去的东西大了六倍。
一个都没有出现的编码
这次请求里声明了能解br、gzip、deflate,没有声明zstd。而实际返回里zstd一个都没有出现,全是br和gzip。
规则不合就静默降级,八大类规则一键扫出不合规的地方是另一处同样的静默。
能力申报决定拿到什么,抓取和渲染分几步走里也有同样的机制。
这个结果本身在意料之中——服务器只会从你声明能解的那几种里挑。但它顺带演示了内容协商最基本的一条规则:你没说自己能接受的,服务器就不会给。能力是客户端主动申报的,服务器不会去猜。
这条规则的另一面是,如果你的客户端申报能力时漏了什么,你就永远拿不到那种形式的响应,而且不会有任何提示。这在写抓取脚本时是个常见的坑:默认不带压缩声明,然后被自己拉走的流量吓一跳。
压缩比这个数还能用来做什么
6.44倍这个中位数值得多说一句,因为它比想象中有用。
字节数最后要折成资源账,页面碳足迹与爬虫抓取的同一套规范换了个角度算同一笔。
体积在抓取那边也有硬上限,抓取正文只读前两兆是必须知道的一条。
首先它能反推页面里冗余的量。文本压缩靠的是重复模式,压缩比越高说明重复越多。一个能压到六分之一的页面,意味着它的HTML里有大量重复结构——重复的类名、重复的内联样式、重复的属性。
其次它是判断某些性能问题的快速参照。如果一个页面的压缩比明显低于这个区间,比如只有两三倍,那多半是页面里塞了大量内联的图片数据或者已经压过一遍的内容,这类东西再压也压不动。
最后它能帮你估算流量账。把日志里的字节数除以六,才是这些请求真正对应的内容量;反过来,如果发现某类请求的字节数没除以六还偏高,那就是没走压缩的那一批。
不写Cache-Control的那30个站,缓存怎么办?
顺着缓存这条线再往下一层。
应用层的缓存同样要显式配置,对象缓存的原理与运维是另一层的同类工作。
21.6% 的站没有这个头
139个站里,30个(21.6%)的首页响应完全没有 Cache-Control。
先把该测的测起来,三类工具与追踪习惯是入门的顺序。
不配置就走默认这件事到处都是,字节码缓存怎么调才真的快是又一个例子。
没有这个头,缓存不会因此就不缓存,它会走启发式规则:按修改时间与当前时间的差值取一个比例当作可缓存时长,常见取10%。一个上周改过的页面,就可能被缓存好几个小时。
而这次的样本里,带 Last-Modified 的只有17个站(12.2%)。既没有 Cache-Control 又没有 Last-Modified 的情况下,各家缓存的处理并不一致,有的不缓存,有的给一个很短的默认值。
这是本文里最纯粹的一个缺省分支:你什么都没说,于是每一层缓存各自决定了一个值,而这些值你既不知道也不一致。
写了的那109个站写了什么
剩下109个站的取值分布很说明问题。
首页承担的转化任务决定了它的策略,双轴八模块九十天实战能看到取舍来自哪里。
不缓存的代价最终体现在体验指标上,行业基准与投入产出测算可以拿来算这笔账。
| Cache-Control取值 | 站数 |
|---|---|
| private, no-store | 36 |
| no-cache, no-store, must-revalidate | 9 |
| public, max-age=0, must-revalidate | 12 |
| no-store, no-cache, must-revalidate, max-age=0 | 4 |
| no-cache, no-store, max-age=0, must-revalidate | 3 |
把这几行读一遍会发现一个共同点:绝大多数都是各种写法的“别缓存”。首页作为一个高流量入口,被明确要求每次都回源。
原因不难理解:首页上有购物车数量、有会员状态、有个性化推荐,缓存一份发给所有人是要出事的。所以大家选了最安全的做法——干脆不缓存。
但这个选择的代价是每一次首页访问都要打到源站。如果Vary这套机制被正确使用,本来是可以做到既缓存又分版本的,只是那需要把随什么变这件事想清楚并且写下来,比统一no-store麻烦得多。
ETag有一半的站在发,它派什么用场
样本里 ETag 的覆盖率是51.8%,比 Last-Modified 的12.2% 高出一大截。
修改时间这类字段在别处也要写对,两千四百个站踩过的sitemap坑里有相关的一条。
验证机制是否真的生效要看实测,三平台三百站的收录实测是同一种验证思路。
这两个字段解决的是同一件事:让客户端下次请求时能问一句“我手上这份还能用吗”,服务器如果说能用,就回一个很短的响应,正文一个字节都不用传。
ETag 覆盖率高的原因和前面那条规律一致——它是服务器根据响应内容自动算出来的,不需要人参与;而 Last-Modified 需要知道内容的修改时间,动态页面没有这个概念。
不过要注意一个坑:如果一个站有多台服务器,各自算出的 ETag 可能不一样,那么同一份内容在不同服务器上会被当成不同版本,验证永远失败,这个机制就白装了。多机部署时要么统一算法,要么干脆关掉它,别留着一个永远命中不了的验证器。
缓存命中率的实际情况
响应里还能读到缓存层的命中状态。这次能读到的分布是:DYNAMIC 77个、HIT 18个、MISS 19个、BYPASS 2个,另外 Age 头出现在32个站(23%)。
回源多了抓取那边也会受影响,抓取预算优化的十二项实操是关联的一头。
命中率低通常是配置没打通,索引器、缓存与反向代理调优是排查的顺序。
DYNAMIC 的意思是这个响应压根不进缓存,边缘层直接转给了源站。77个站,超过一半。这和上面 no-store 占多数是同一件事的两面。
首页不缓存,不等于整站都不该缓存
这里有个容易被顺手带过去的问题:很多站把首页设成 no-store 之后,同一套策略被应用到了全站。
把个性化那块单独拆出来是关键,首屏内容怎么影响SEO讲的是同一块区域。
首页的特殊性不止在缓存上,首页首屏怎么设计才留住人是另一头的讲究。
但首页和内页的性质完全不同。首页有购物车数量、有个性化推荐,确实因人而异;而一个商品详情页、一篇博客文章、一个帮助文档,绝大多数内容对所有人是一样的,个性化部分往往只是页头的那一小块。
把整站按首页的标准设成不缓存,等于为了页头那一块状态放弃了整页的缓存收益。更合理的做法是把因人而异的那一小块拆出来单独请求,页面主体正常缓存。
判断方法很直接:把一个内页用两个不同的会话打开,逐段比对哪里不一样。如果只有页头几个数字不同,那这页是可以缓存的。这个比对花不了十分钟,而它决定的是这个站有没有边缘缓存这件事。
Age头能告诉你什么
顺带说说 Age 这个字段,样本里23% 的站发了它。
看懂一个指标要先知道它怎么算出来的,从建项目到看懂核心指标是同一种读法。
想看某个时点的页面长什么样,缓存退役后历史快照还能怎么查列了几个工具。
它的含义是这份响应在缓存里已经待了多少秒。数值大说明这份副本很旧、缓存命中率高;一直是0或者根本没有这个头,说明每次都是新取的。
它的实际用途是当作缓存策略的验证器:你设了一小时的缓存时长,那么反复请求同一个地址,Age 应该在0到3600之间爬升,然后归零。如果它始终是0,说明缓存没生效,得回去查是不是被某个头挡住了。
这一项和前面讲的 Vary 是一对:Vary 决定缓存怎么分版本,Age 告诉你分完之后有没有真的命中。只看配置不看 Age,你不知道自己的缓存策略是不是纸上谈兵。
首次请求就写下的778条Cookie,是谁写的?
换一个维度看缺省。这一次不是响应内容,是响应带来的副作用。
要管住这些东西得先有个管控平台,四家主流同意管理工具横评是选型的起点。
85.6% 的站在第一次请求就发Cookie
这次的请求是干净的:没有历史Cookie,没有登录态,没有任何交互,就是一次首页GET。
前端这类提示的默认状态也该管,评论Cookies提示的汉化与默认勾选是个小而具体的例子。
同意横幅和数据采集怎么共存,同意模式下的出海合规架构给了完整方案。
结果139个站里有119个(85.6%)在这次响应里就写下了Cookie,条数中位是7条,最多的一个站写了39条,全样本合计778条。
写得最多的几个:weber.com 39条、theordinary.com 24条、suitsupply.com 19条、cybex-online.com与joolz.com各18条。
这些Cookie是干什么的
把名字汇总去重排个频次,前几位非常整齐。
每装一个工具就多一批痕迹,两种安装方式的取舍里能看到它带进来什么。
装的应用越多留下的东西越多,应用栈精简与冲突排查是清理的做法。
| Cookie名 | 出现站数 | 用途 |
|---|---|---|
| _shopify_essential | 96 | 店铺平台必需会话 |
| localization | 53 | 记住地区选择 |
| cart_currency | 52 | 记住货币 |
| _shopify_y / _shopify_s | 各46 | 访客与会话标识 |
| _shopify_analytics / _marketing | 各45 | 分析与营销归因 |
| __cf_bm | 26 | 边缘层机器人识别 |
这份名单里几乎没有站主自己写的东西。它们全部来自建站平台和边缘服务商,是开店和接入CDN时自带的。
顺带说一个数字上的意外:_shopify_essential 出现在96个站,而前一轮从robots.txt文件特征识别出的同平台站点只有65个。两个探针给出的平台占比差了将近一半。原因是有些站改写了robots.txt,指纹认不出来,但Cookie骗不了人。这也算一个小的方法论提醒:判断技术栈别只用一个探针。
为什么这份名单里几乎没有自己写的东西
值得停下来想一下:为什么778条Cookie里,站主自己写的凤毛麟角?
平台替你决定的东西还有很多,集合页没有产品时的三种场景是另一个例子。
平台替你做了很多决定,在这套系统上同时做好搜索与AI优化是接受这个前提之后的打法。
不是因为大家不需要存状态,而是因为需要存的那些状态,平台已经替你存好了。购物车、货币、地区、会话——这些是电商的通用需求,平台把它们做成了默认能力。
这本身是好事,它意味着你不用重复造轮子。但它带来两个后果。一是你对这些状态的控制力其实很弱,改名、改有效期、改作用域都得看平台支不支持;二是当出问题时,你需要先搞清楚这条Cookie是谁的,而这件事没有现成的对照表。
所以做一次盘点是值得的:把首次响应的Cookie名字列出来,逐条查它属于哪个系统。查的方法很土,搜名字就行——这些名字在各家文档和社区里都能找到。
盘完之后你会得到一份自己站上的状态地图。它的价值不在盘的那一天,而在半年后某次排查会话丢失问题时,你能立刻知道该看哪一条。
Domain属性没写的有537条
778条Cookie里,537条没有写 Domain 属性。
身份打不通归因就会错,多触点归因模型怎么选是这条链路的下一步。
跨子域的身份打不通会毁掉分析,服务器端跟踪还原完整链路的八步是修复方向。
不写会怎样?缺省是只作用于发出它的那个主机名,不包含子域。写了 Domain=.example.com,才会在所有子域之间共享。
这个缺省方向是安全的——不写等于范围更小。但它也意味着如果你的站有多个子域,不写这一项的Cookie在子域之间是不通的。购物车在 shop.example.com 上,博客在 blog.example.com 上,两边的会话标识各是各的,跨子域的行为分析会看到两个不相干的访客。
安全属性方面:778条里带 Secure 的488条,带 HttpOnly 的313条。SameSite 的取值分布是 Lax 323条、None 246条、Strict 只有1条。Lax 排第一,正是因为它是现代浏览器不写这一项时的缺省值——写与不写结果一样,所以大家更愿意显式写出来。这算是本文唯一一个大家把缺省显式化做得不错的地方。
安全属性的覆盖率说明了什么
778条里带 Secure 的488条、带 HttpOnly 的313条,占比分别是62.7% 和40.2%。
比例数字要结合意图读,合规边界与决策红线也是靠意图划线的。
安全项要看具体用途再判断,八类内网地址绕过的修补也是一条条看出来的。
这两项的差距有它的道理。Secure 限制这条Cookie只在加密连接上发送,几乎没有副作用,平台默认打开的多;HttpOnly 禁止页面脚本读取它,而很多Cookie恰恰就是给前端脚本用的,比如记住地区选择、控制弹窗显示,加上这个属性就用不了了。
所以40.2% 这个数不是“有六成没做好”,而是“有六成本来就要给脚本读”。这类比例数字必须结合用途来读,否则很容易得出一个听着吓人但没意义的结论。
真正该关心的是那些明显属于会话或者身份的Cookie有没有这两项。这份判断没法靠统计得出,得逐条看名字——而这正好又绕回前面那件事:先有一份自己站上的Cookie清单,后面所有判断才有依据。
第三方域的Cookie只有4个站
按域归类之后,含第三方域Cookie的站只有4个,其余115个全是第一方。
第三方追踪失效之后要重建链路,九招重建接口与投放回报是那条路的做法。
第三方脚本留下的痕迹不止Cookie,第三方统计图标的现代处理与合规是另一处。
这个结果比预期干净,原因是首次请求还没触发那些延迟加载的第三方脚本。要看真实的第三方Cookie数量,得在浏览器里跑完整个页面生命周期,而不是一次纯请求。这也是这份数据的口径边界,得说清楚。
七条Cookie意味着每个请求多带多少字节
Cookie有一个常被忽略的成本:它们会被带在此后每一个同域请求的请求头里,包括图片、脚本、样式表。
首屏的每一点开销都要算,搜索框从入口到结果的四层拆解是另一个高价值入口。
第三方脚本对性能的拖累是叠加的,五个场景的弃用接口治理是逐个拆的过程。
按这次的中位数7条算,一条平均几十字节,加起来一个请求要多带几百字节。一个页面加载过程中如果有一百个同域请求,光Cookie就是几十KB的上行流量。上行带宽通常比下行小得多,这部分开销对移动端首屏的影响比多数人以为的大。
写了39条的那个站,情况会明显一些。这类问题的解法通常是把静态资源放到一个独立域名下——不同域,Cookie就不会被带过去。这是Cookie那个 Domain 缺省行为的一个正面用法:默认不跨域,正好帮你隔离。
同意条弹出之前就已经写好了
还有一层要说清楚:这778条Cookie是在第一次请求的响应里就下发的,也就是用户还没看到页面、更没点过任何同意按钮的时候,它们已经在浏览器里了。
弹窗时机与合规是同一件事,时机、字段与移动端合规实战给了具体做法。
这类问题最终要法务来定边界,隐私合规与应答的七个动作点是配套的分工。
这在合规上不是自动出问题,因为各地规则通常对严格必要的那一类留了口子——会话标识、购物车、安全防护都属于必要范围。样本里排在前面的几条大多能归到这一类。
但排在后面的那些就未必了。名单里明确带分析和营销字样的字段出现在四十多个站上,这一类通常不在必要范围内。它们出现在同意之前,多半不是有意为之,而是平台默认打开、没人去关。
该做的事很简单:把首次响应的Cookie清单导出来,逐条标注它属于哪一类,不属于必要类的挪到同意之后再写。这件事的难点从来不是技术,是没人知道首次响应里已经写了这么多条。
一次响应里36个头字段,你认识几个?
把镜头再拉远一点,看看一次普通的首页响应到底带回来多少东西。
技术审查要多看几层,从AI爬虫到无障碍的五个新层面是重新划分之后的清单。
中位36条,最多235条
139次响应里,头字段条数的中位是36,最少的一个站只有10条,最多的一个站有235条。
响应里还有一大块是结构化数据,一百二十八种类型怎么选是那边的取舍。
层数一多就得靠自动化守住,把配置纳入持续集成的做法是治本的那一档。
全样本出现过的不同字段名有272种。其中标准定义的只是一小部分,剩下大量是各家平台、CDN、安全产品、前端框架自己加的。
覆盖率最高的那一批
| 响应头 | 覆盖率 | 谁加的 |
|---|---|---|
| Content-Encoding | 98.6% | 服务器自动 |
| Server | 92.8% | 服务器自动 |
| Vary | 92.1% | 服务器自动为主 |
| Strict-Transport-Security | 90.6% | 平台或人工 |
| Set-Cookie | 85.6% | 平台自动 |
| X-Content-Type-Options | 80.6% | 平台或人工 |
| Cache-Control | 78.4% | 需要人工 |
| Content-Security-Policy | 67.6% | 需要人工 |
| ETag | 51.8% | 服务器自动 |
| Last-Modified | 12.2% | 需要人工或静态文件 |
从主机到插件的默认值一样要过一遍,从主机、插件到体验指标是那套系统的清单。
开发期就该定下来的那些项,十大优化要点清单列得比较全。
这张表按覆盖率排下来,正好呈现出一条规律:自动加的都在九成以上,需要人写的掉到七成以下,需要人写且没有默认模板的掉到一成多。
这条规律和上一轮在robots.txt上看到的完全一致——平台默认写得挺全,自定义的那部分才是缺口所在。
头字段数量的两端分别是什么站
中位36条,最少10条,最多235条。这个跨度本身有信息量。
架构简单与功能丰富之间要选一头,九个维度的选型对比把代价摆出来了。
架构选型直接决定要自己搭多少,基建得自己重搭的那几样是选之前该知道的。
只有10条的那一端,通常是把静态文件直接放在对象存储或者简单静态托管上的站。没有应用层、没有安全模块、没有会话,响应头就是最基本的那几个。这类站的行为最可预测,因为几乎没有东西在中间做决定。
235条的那一端则是经过了多层处理的:源站加一批、应用框架加一批、安全产品加一批、边缘节点再加一批诊断字段。层数越多,前面说的“某一层重写时丢了东西”的概率就越高。
这两端之间存在一个真实的取舍:功能越多、层次越多,能力越强,但行为的可预测性越差、需要实测的维度越多。没有哪一端天然更好,但至少要知道自己在哪一端,以及这意味着要做多少验证工作。
一个快速自查:数一下自己首页响应的头字段条数。明显超过样本中位数的话,说明你的响应经过的处理层比多数同行都多,那么本文讲的这套实测对你的价值也更大。
边缘层是谁
顺手记一下这批站的边缘归属:Server 头里cloudflare占62.6%(87个站),netlify 6个,cloudfront 5个,vercel 5个,tengine 4个。按CDN特有的指纹头统计,Cloudflare 90个、CloudFront 13个、Fastly 8个、Vercel 8个、Akamai 1个。
不同入口拿到的东西也不一样,隐私搜索引擎要不要单独做是另一个分岔。
托管选型也是这一层的一部分,国内主机还是国外主机的取舍有具体判断。
HTTP版本上,133个站走HTTP/2,只有6个还在HTTP/1.1。Content-Type 里带 charset 的115个(82.7%),没带的24个——不带的话浏览器要靠猜,虽然现代浏览器猜得挺准,但这仍然是一个把决定权交出去的缺省。
64% 的站宣告了下一代协议,这次一次都没用上
这里有个特别能说明问题的对照。
两端约定不一致就会退回默认,三类跳转方案对比里也有同样的协商过程。
两端能力不一致导致结果不同,移动端与桌面端排名差异的六大因素是同一种成因。
响应头里有个字段叫 Alt-Svc,作用是告诉客户端“我还支持另一种更快的连接方式,下次可以试试”。样本里89个站(64%)发了这个字段,宣告支持HTTP/3。
而实际连接统计是:133个走HTTP/2,6个走HTTP/1.1,HTTP/3一个都没有。
原因不是这些站在说谎,是这次用的客户端默认不启用那个协议。服务器宣告了能力,客户端没接这个话,于是双方退回到都支持的那一档。
这个例子和前面的zstd是同一件事的两面:一边的能力宣告,加另一边的能力申报,交集才是实际发生的行为。只看任何一边都会得出错误结论——只看服务器会以为大家都在用新协议,只看客户端会以为服务器不支持。
272种字段名里,有多少是给人看的
272这个数字里,标准定义的字段大概占几十个,剩下两百多个全是各家自定义的。
该关掉的能力就关掉,五种环境禁掉目录执行权限的写法是同一种收敛。
暴露版本信息是要处理的,版本隐藏与访问控制实战给了具体做法。
它们大致分三类。一类是诊断信息,比如请求追踪号、处理它的节点编号、耗时统计——这些是给服务商自己排障用的。一类是安全产品加的标记。还有一类是前端框架为了自己的路由机制加的,样本里那6个写了 Next-Router-State-Tree 的站就属于这一类。
对站主来说,这些字段绝大多数不需要关心。但有两件事值得做一次:一是检查有没有暴露内部信息的字段,比如具体的框架版本号、内网主机名、后端服务名;二是估一下这些头字段的总字节数,中位36条,最多那个站235条,后者的响应头体积已经能和一个小图标相提并论了,而且每个请求都要发一遍。
安全头那一组的覆盖率为什么高
顺带看一眼安全相关的几项:Strict-Transport-Security 90.6%、X-Content-Type-Options 80.6%、Content-Security-Policy 67.6%、X-Frame-Options 62.6%。
容易做的先做完再看别的,九个被低估的技巧里有几条也是低成本高回报。
有一键方案的项覆盖率总是高,落地页体验的及格线也是被平台规则推着做起来的。
这几个数明显高于 Cache-Control 的78.4% 之外的其他人工项,原因很简单:它们在主流平台和CDN的控制台里是一个开关,点一下就全站生效。
反过来看 Last-Modified 只有12.2%,因为它没法一键开——它需要服务器知道这份内容什么时候改的,而动态生成的页面根本没有这个概念。
所以覆盖率高低基本等于“有没有人做成一键开关”,和这项配置本身重不重要关系不大。这条规律在评估任何一份行业统计时都用得上:先问这一项是不是有默认或者一键方案,再看数字。
这些不成文的缺省,到底是谁定的?
把前面几节的结果并到一起看,会发现这些行为的来源相当集中。
平台层的坑跨系统是相通的,建站第一年最容易失分的十二项是同一批经验。
三个来源,各管一段
第一个来源是建站平台。它决定了Cookie写几条、叫什么名字、有没有 Secure;也决定了首页默认的缓存策略是不是 no-store。这一层的特点是全平台统一,你和用同一套系统的几万家店拿到的是同一份行为。
换一层底座要有回滚预案,十二步迁移与回滚演练是这类变更的标准做法。
第三层的活要拉后端一起做,工程侧七个动作点的分工能省掉来回。
第二个来源是边缘服务商。它决定了用br还是gzip、要不要自动补 Vary: Accept-Encoding、机器人识别标记怎么下发、缓存命中状态怎么标。样本里62.6% 的站在同一家CDN后面,这意味着这批站的这一层行为高度一致。
第三个来源才是你自己的代码。按语言分发、按地区换货币、按登录态改页面——这些是业务逻辑,只有这一层的变化是你亲手写的。
而这三层里,只有第三层的变化需要你手动声明,也只有第三层会漏。前两层的行为虽然不是你定的,但它们自带了配套的声明;你自己写的那部分反而没有任何东西提醒你去补。
三层各自的可预测性
| 来源 | 典型行为 | 可预测性 | 会不会自己变 |
|---|---|---|---|
| 建站平台 | Cookie、默认缓存策略 | 同平台一致,可查同行 | 会,随平台版本更新 |
| 边缘服务商 | 压缩、Vary自动补、缓存标记 | 同服务商一致 | 会,随产品策略调整 |
| 自己的代码 | 语言、货币、个性化 | 只有你自己知道 | 只在你改的时候变 |
不可预测的部分最终会体现在预算上,预算到归因到退出的七个动作是那本账。
把这类工作当产品来排期,产品化指标体系与迭代节奏是可用的方法论。
这张表里最值得留意的是最后一列。前两层会在你完全没动过任何东西的时候自己变化,而且不会有人单独通知你。换句话说,就算你今天把所有行为都测清楚了,这份基线也有保质期。
为什么第三层最容易漏
前面说只有第三层会漏,这句话值得再拆一层,因为它解释的不只是Vary这一件事。
断点在人不在技术,四画像三阶段的团队路线图是补人这一环的思路。
反馈链条断掉是协作问题,流量到转化的边界与交点说的是另一处断点。
平台和边缘服务商在设计自己的功能时,是把“这个功能需要配套什么”当成一个完整包交付的。他们做压缩,就顺手补上压缩的声明;他们下发Cookie,就顺手带上安全属性。因为对他们来说,配套没做全会变成成千上万客户的工单。
而你写业务代码时,没有这个压力。按语言返回不同内容,写完测一下,浏览器里显示正确,功能就算完成了。“这个变化要不要告诉缓存”这个问题,不会在任何一个测试环节里被问到——本地开发没有缓存,测试环境的缓存通常也关着。
等到上了生产、走了边缘缓存,问题才有条件出现,而那时距离写代码已经过去很久,没人会把两件事联系起来。
所以这不是能力问题,是反馈链条断了:做决定的时刻和暴露后果的时刻,中间隔着一层只在生产环境才存在的东西。凡是有这个结构的地方,都值得单独加一道检查。
怎么判断一个行为是哪一层来的
有个简单的分辨法:去找几个同平台或同CDN的站测一遍同样的东西。如果结果一致,那是平台层的;如果只有你这样,那是你自己代码里的。
同平台的站长得很像,比一比就知道,结构化数据的八步实战就是这么摸出来的。
托管方悄悄改了策略这类事,监控没报警但引用归零是最近一个例子。
这个方法在本文里用过一次,效果不错。_shopify_essential 这个Cookie出现在96个站上,几乎所有用这套系统的站都有,一眼就能判定是平台行为,不用去翻自己的代码。
反过来,delonghi.com那个体积差59% 的表现只有它一家,那必然是它自己的业务代码在做语言分发。分辨清楚来源,才知道该去改谁、该找谁问、以及这个行为哪天可能自己变掉。
平台层的行为会变,而且不通知你
前两层会自己变这件事,值得单独强调,因为它决定了这套测试必须定期跑而不是做一次。
规则说变就变,返回按钮劫持新规的合规排查是一次具体的应对。
外部变化要靠追踪才看得见,各家波动追踪工具与解读流派是这套监测的参照。
可能发生的变化有这么几类:平台改了Cookie的名字或者数量、CDN把默认压缩算法从一种换成另一种、边缘产品调整了自动补 Vary 的策略、安全模块开始给某类请求加新的标记。
这些变更通常会出现在服务商的更新日志里,今年那次边缘层默认设置变更就是先发了公告再引发一批抓取异常;但那份日志的读者是运维,不是做站的人;而且它面向所有客户,不会告诉你“这条对你的站意味着什么”。公告发出来了不等于送达了,送达了不等于被翻译成了你这边的行动项。
所以真正可靠的做法不是订阅公告,是定期对自己的站做一次同样的测试,用差分捕捉变化。公告是通用的,差分是你自己的;前者你可能漏读,后者一定和你相关。
这一点也是本文这套方法最实际的价值所在——它测的不是别人的站,是你自己那一份行为清单会不会在你没注意的时候被人改掉。
这些缺省该怎么测出来?
讲完数据,说做法。这一节是全文最能直接用的部分。
另一条实测路径是读日志,五千个站的爬虫伪造与抓取预算实测是那条路的样板。
测试矩阵怎么搭
核心思路只有一句:把请求的每一个变量单独变一次,其余保持不动,看响应变不变。
把一个问题拆成多个维度是通用手法,一个主题挖五十个长尾问题的五种方法是另一处应用。
变量多的时候要设计抽样,跨设备位置怎么省钱的采样设计是同一种思路。
需要变的变量,按重要性排是这几个。
| 变量 | 怎么变 | 看响应的什么 |
|---|---|---|
| Accept-Encoding | 带br、只带gzip、完全不带 | Content-Encoding、体积 |
| Accept-Language | 英语、目标市场语言、不带 | 最终地址、html lang、标题、体积 |
| User-Agent | 桌面、移动、搜索爬虫 | 最终地址、体积、结构化数据 |
| Cookie | 无、带地区选择、带登录态 | 价格、货币、库存显示 |
| 来源地址 | 不同地区的出口 | 最终地址、货币 |
每一行测完,得到的是一个是非题的答案:这个变量会不会改变响应。把答案汇总起来,就是你这个站真实的变化维度清单。
比对什么才算数
这一步是前面吃过亏的地方,直接给结论。按可靠性从高到低排:最终地址 > 页面语言标记 > 页面标题 > 正文体积 > 正文指纹。
抽取稳定字段来比对更省事,一次扒清五种格式的字段缺漏就是这么做的。
要比对就得先有稳定的快照,把搜索结果页变成可对账的时间资产讲的是存证工程。
前四项都不可能被页面里的随机串影响,可以直接采信。最后一项要用就必须先把随机字段清理干净,而清理干净这件事比想象中难——本文的第一版尺子就是在这里虚报了3.8倍。
更省事的做法是不做全文指纹,改成只提取几个关键位置:html 标签的属性、title、meta description、页面上第一个价格数字、货币符号。这几个东西稳定、有语义、也正好是你真正关心会不会变的东西。
然后和Vary对一遍
拿到变化维度清单之后,去读一遍自己的 Vary 头,逐项核对。
声明之间还会互相打架,两个标记的九种场景判断把边界划清了。
声明与实际对不上是通病,八种跨页场景与冲突诊断是另一个字段上的同款问题。
结果无非三种。清单里有而 Vary 里没有,是漏声明,缓存会串版本,必须补。Vary 里有而清单里没有,是过度声明,缓存被切得太碎、命中率变低,可以删。两边一致,这一项就算过了。
这个核对的价值在于它给出的是行动项,不是分数。每一条差异都对应一个明确的改动,不存在模棱两可的中间状态。
把它做成一次可重复的检查
手工测一次能发现当下的问题,但缺省是会被改的——换一次CDN、加一层边缘脚本、升一次框架版本,行为都可能变。
定时任务这类活要考虑失败重试,实时推送与异步队列怎么搭有可参考的结构。
定时跑加存档靠计划任务就够,用cron把独立站运维自动化给了脚本骨架。
所以这套矩阵值得写成脚本,跑在部署流程里或者定时任务里,输出存档,和上一次的结果做差分。发现响应行为变了但没人提过这次改动,就是最值得追查的那一类信号。
用什么工具
不需要专门的软件,命令行的HTTP客户端就够,关键是把几个选项用对。
数据落地之后还要能关联起来,逐项打通几个后台的操作是下一步。
把结果做成自己的报表也不难,用命令行工具做自定义SEO报表是一条可行路径。
要点有四个。第一,把响应头单独存到文件里,别只看正文——本文一半以上的结论来自响应头。第二,跟随跳转,同时把最终地址记下来,因为跳转本身就是一种变化。第三,需要显式控制请求头时,把不需要的那个字段设成空值而不是不设,两者在多数客户端里含义不同。第四,把状态码、体积、耗时这几个数值一次性格式化输出,方便直接落成表格。
并发上要克制。同一个站点连续快速请求几次很容易被边缘防护判成异常,本文这次是每个站点内部串行、站点之间并发三路,中间留了间隔,全程没有被限流。测自己的站可以放开,测别人的站必须慢。
什么时候跑
时间点比想象中重要,有三个时机值得固定下来。
迁移这类大动作前后都要测,六维度保稳的完整路线图列了时点。
改版之后必须整体复测,改版不掉流量的完整防护清单列了要复查的项。
第一个是每次部署之后。这时候要验的是自己的改动有没有意外改变响应行为,尤其是碰过中间件、路由或者边缘脚本的时候。
第二个是换了任何一层基础设施之后。换CDN、换主机、加一层防护,这些动作会整批地替换掉前面说的第二个来源的行为。这类变更最容易在事后很久才发现问题,因为当时页面看起来一切正常。
第三个是定期的无事巡检,频率不用高,一个月一次足够。它抓的是前两个来源在你没动手的时候自己发生的变化。
结果怎么读才不误判
有两类假信号要先排掉,不然每次差分都会报一堆问题。
异常曲线要能和正常波动分开,增长速度异常的识别与防护是同一个判别难题。
灰度和实验会互相干扰,三十个A/B测试方案里有分流设计的讲究。
一类是页面里的随机值,前面讲过了,靠只提取稳定位置来避开。另一类是灰度发布——很多站在同一时间会有多个版本在跑,两次请求可能落到不同版本上,看起来像是随请求变,实际上只是随机分配。
区分这两者的办法是重复:同一个变体连发三次,如果三次之间也不一致,那就不是协商造成的差异,是随机分配。只有同一变体内部稳定、变体之间不同,才能判定为真正的按请求变化。
这一步会让测试次数翻三倍,但它是把结论从“看着像”变成“确实是”的唯一办法。本文的数据没有做这一步,所以严判据里那几个只靠体积差判定的站,严格说仍有灰度发布的可能——这一点必须说明,不能拿一个自己都没排除干净的数当结论。
一份可以照着跑的实测清单
把上面的东西压缩成能直接执行的步骤。
清单跑完要排先后,三类站点的高回报修复顺序是可用的排法。
五分钟版
只做三次请求,对同一个首页地址。
轻量选择也会影响看到的数据,两种资源类型的六场景选型是选之前该知道的。
轻量检查一样能查出大问题,死链批量检测、分类到提交也是几条命令的事。
第一次用正常的浏览器请求头。第二次把 Accept-Language 换成你主要市场的语言。第三次把 Accept-Encoding 整个去掉。
三次都把响应头存下来,看三件事:三次的最终地址一样吗,三次的 Content-Encoding 一样吗,响应里的 Vary 写了什么。
只要出现地址不同或编码不同,而 Vary 里没有对应字段,就是一个待修项。这三次请求能覆盖掉本文里说的绝大部分问题。
五分钟版能查出什么,查不出什么
把预期说清楚,免得跑完觉得没发现问题就以为没事。
筛子和体检报告要分开看,五类问题URL的占比与排查顺序是更完整的那一份。
这三次请求能查出的是:语言维度和压缩维度上的漏声明,以及缓存策略是显式设定还是默认捡来的。按本文的数据,这两个维度覆盖了实测中发现的绝大部分静默变体,投入产出比最高。
查不出的有三类。一是按来源地区分发的行为,那需要从不同地区的出口发请求;二是按登录态变化的内容,那需要带真实会话;三是灰度发布造成的差异,那需要同一变体重复多次才能区分。
另外它只测了首页。首页往往是全站最特殊的一个页面——最容易被设成不缓存,也最容易有个性化内容。首页的结论不能直接推广到商品页和内容页,那是半天版要做的事。
所以五分钟版的正确用法是当筛子而不是当体检报告:发现问题说明确实有问题,没发现问题只说明这两个维度是干净的。
半天版
在五分钟版的基础上加四件事。
内页里最复杂的是筛选那一类,筛选URL不爆炸的系统方案要单独处理。
内页的规则和首页常常不同,分页的索引判断与canonical设置是内页那边要单独看的。
一是把地址从首页扩到几个代表性页面:一个商品详情页、一个类目页、一个内容页。不同页面走的代码路径不同,行为未必一致。
二是加上 User-Agent 这个维度,尤其要测搜索爬虫的令牌。如果响应对爬虫和对浏览器不同,而 Vary 里没有 User-Agent,那是个更严重的问题。
三是把 Cache-Control 和 Last-Modified 一并记下来,确认自己知道每个页面的缓存策略是显式设定的还是默认捡来的。
四是把首次请求写下的Cookie列出来,逐条问一句这是谁写的、干什么用的。问不出来的那几条,通常来自某个已经不用了的第三方脚本。
顺手能查出来的另外三件事
同一批请求已经发出去了,多看几个字段几乎不额外花时间,顺手把这三件事一起查了。
跳转与错误页要一起看,404修复与软404排查是配套的操作。
跳转链一起看能省一轮,301、302、404与410各自的含义是判断依据。
第一件是跳转链的长度。请求最终落在哪个地址、中间跳了几次,客户端都会告诉你。超过两跳就值得看一眼,每一跳都是一次完整的往返,对移动端首屏的影响很直接。
第二件是响应头里有没有暴露内部信息。具体的框架版本号、后端服务名、内网主机名,这些在272种字段里偶尔会冒出来,删掉不影响任何功能。
第三件是 Content-Type 有没有带字符集。样本里有24个站没带,虽然现代浏览器猜得挺准,但这是个一行就能补上的东西,没有不补的理由。
这三件事的共同点是:它们都不需要新的测试,只需要在已有的响应里多看两眼。做一次实测就把能顺出来的都顺出来,比分三次做省事得多。
做完之后写在哪
结果不要只存在某个人的终端历史里。落到一份文档里,内容就三列:变量、是否影响响应、Vary里有没有声明。
零散的东西要有归拢的结构,主题集群和支柱页照着搭为什么没效果讲的是结构的要害。
把零散知识收成可查的一处,把孤岛词条织成主题权威是同一种整理动作。
这份文档的作用不是给别人看,是给下一次改动时的自己看。当有人问“加一层地区判断会不会有事”,你能立刻回答“会,而且要同步改Vary”,而不是重新测一遍。
这份清单该由谁维护
实际操作中,这件事经常卡在归属上:它算前端的、后端的、运维的,还是做站的?
归属要落到配比上,三类分工与团队配比的决策是配套的组织答案。
归属不清就等于没人管,团队怎么搭才出活说的是这件事的组织解法。
从产生变化的位置看,语言分发多半在后端或者边缘脚本里,缓存策略在运维或者CDN控制台里,Cookie来自平台和第三方脚本。没有任何一个角色能独立说清全部,这正是它长期没人管的根本原因。
可行的安排是:让做站或者技术负责人持有这份清单,各个角色在自己动手时负责更新对应的行。清单本身放在版本库里,和代码一起走评审流程。
更重要的是把它挂进现有流程:在改动清单涉及的那几层时,评审模板里加一句“本次改动是否改变了响应随请求变化的维度”。一句话的成本,换的是这件事从此有人过问。
这个安排听起来很轻,但它是前面所有测试真正落地的前提。测出来的结论如果没有归属,下一次基础设施变更之后它就作废了,而作废的时候没有任何人会知道。
测出来之后,该改哪一层?
最后一节讲修复的落点,因为同一个问题在不同层修,代价差很多。
服务器这一层能做的事不少,六层重写与缓存的综合治理是可照抄的分层。
能在源站改就别在边缘改
Vary 这个头最好由产生变化的那段代码同时发出。谁决定了返回哪个版本,谁就负责声明这件事,两段逻辑放在一起,改的时候不会漏。
跳转放哪一层也是同一个选择,双向跳转的完整配置能看出层次差异。
反代那一层改响应头要当心,末尾斜杠、重写与全场景配置有具体写法。
放到边缘层去补也能生效,但那意味着两处逻辑要靠人记住保持同步。本文里那17个编码变了却没声明的站,成因大概率就是响应头在某一层被重写时丢了东西。每多一层重写,就多一处需要同步的地方。
如果非要在边缘改
那就把规则写成追加而不是替换。Vary 是个可以有多个值的字段,追加一个令牌不会影响已有的,替换整行就会。
边缘规则常被用来兜底,三类筛选URL的分流处理策略是更靠前的解法。
边缘层能做的远不止改头,用内容协商给智能体发Markdown是另一个方向的用法。
另外要注意执行顺序:边缘脚本改响应头的时机,可能在缓存写入之前也可能在之后,这一点各家产品不一样,直接决定了你的改动有没有作用于缓存键。这也属于文档不写、只能实测的那一类,改完必须验一次。
修复的优先级怎么排
如果一次测出来好几处差异,别按发现顺序改,按影响面排。
影响面大的先修,索引膨胀的诊断与处置矩阵给了同样的排序逻辑。
排完得讲清楚为什么这么排,四板块汇报模板能把这件事说明白。
最优先的是会给错人的那一类:语言、货币、价格、库存。这几样发错版本会直接影响下单,而且用户不会来告诉你,他只会走掉。
第二优先的是会让所有人变慢的那一类:压缩声明漏了,缓存里存了未压缩副本。它不影响正确性,但影响每一个访客。
第三优先的是只影响缓存命中率的那一类:声明写多了,缓存切得太碎。这一类不会出错,只是回源多了一些,可以放到有空的时候再优化。
最后才是那些字段整理类的:删掉暴露内部信息的头、补上字符集声明、清理已经不用的Cookie。这些做起来最轻松,也最容易被优先做掉——但它们的收益也确实是最小的。排期时留意别本末倒置。
把语言分发从内容协商换成显式地址
还有一条更彻底的路:不要按 Accept-Language 变内容,改成每种语言一个独立地址,用跳转把人送过去。
独立地址多了标注就得自动生成,从爬虫结果自动生成多语言sitemap是省人力的做法。
独立地址也不是写了就一定被收,那些语言版本只被当成规范页的别名是要提前知道的。
这样做的好处是同一个地址永远对应同一份内容,缓存不用分版本,搜索引擎也能把每个语言版本单独收录。代价是首页会有一次跳转,而且跳转规则本身又成了一个需要维护的东西。
本文样本里那8个按语言跳到不同地址的站走的就是这条路,从缓存正确性的角度看,它们其实是做对的那一批——虽然它们的跳转规则同样没有出现在任何一份文档里。
改完怎么验证
改 Vary 有个麻烦:改完之后,缓存里还躺着按旧规则存下来的副本。
改完对方认不认是另一回事,规范网址的九大决策逻辑说明了判断权在谁那里。
改动生效要等多久是另一回事,加了noindex之后多久才消失给了实测区间。
所以验证必须分两步。第一步是确认新响应确实带上了正确的头,这一步绕开缓存直接问源站,或者带一个随机查询参数让它必然回源。第二步是清掉边缘缓存,然后按新的变量组合各请求一遍,确认拿到的是各自对应的版本。
第二步经常被跳过,后果是改动看起来生效了、实际用户还在拿旧副本。判断方法是看 Age:如果它是个不小的数,说明你拿到的是清缓存之前的东西。
还有一个容易忽略的点:Vary 只对遵守它的缓存有效。浏览器本地缓存、企业内网的代理、运营商的透明缓存,各自的实现程度不一样。所以对于绝对不能串版本的内容,比价格和库存,最稳妥的做法仍然是根本不缓存,而不是靠声明。
一个不改代码的过渡方案
如果排期紧、暂时改不了应用层,有个成本很低的过渡做法:把首页从内容协商改成固定返回一个版本,然后用页面上的语言切换器让用户自己选。
自动判断猜错的代价很具体,台港用语本地化与那个会转错的词是个鲜活例子。
自动判断这条路的坑不少,假桌面、客户端提示与最佳实践把几种判法都试过了。
这样做牺牲了一点体验——来自其他市场的访客第一眼看到的不是母语版——但换来的是行为完全可预测:一个地址一份内容,缓存不会串,搜索引擎收的也是确定的那一版。
在“自动猜对但可能串版本”和“不猜但绝不出错”之间,多数站选前者是因为默认就是前者,而不是因为比较过。真正比较一次会发现,自动判断带来的体验提升,往往抵不过它带来的缓存复杂度。
这也是本文一直在说的那件事的另一种表现:不是这个选择错了,是这个选择根本没被当成一个选择。
搜索引擎拿到的是哪一版
还有一个角度前面没展开:如果同一个地址有多个版本,搜索引擎收录的是哪一版?
地区变体在检索里会被合并,地区差别写在标注里到了问答只剩一个词是同一个现象。
多语言版本在检索里本来就吃亏,翻译内容为什么在AI检索里吃亏是另一层原因。
答案是它自己那次请求拿到的那一版。搜索爬虫请求时带的语言偏好通常是英语,来源地址多半在美国,所以它拿到的大概率是英语版或者美国版。
这带来两个实际后果。一是你的其他语言版本可能根本没被收录,因为它们没有独立地址,爬虫没有触发它们的条件;二是页面上声明的多语言关系可能对不上——你在页面里声明了有德语版,但那个地址点开对爬虫仍然返回英语版。
本文样本里那4个页面语言标记会变的站尤其要注意这一点:lang 属性从 en 变成 zh-Hans-cn,说明同一个地址下确实有中文版,但这个中文版没有自己的地址,也就没有被单独收录的机会。
要让每个语言版本都能被收录,前提是每个版本有自己的地址。这又绕回了前面那个结论:跳转虽然多一次往返,但它在搜索这件事上是明显更优的选择。
回到那条一般规律
把这一整篇收一下。robots.txt那一层的缺省是成文的,标准文档里写着,查得到只是没人查。而这一层的缺省是不成文的——没有任何一份文档告诉你“这个站随语言变”,它只存在于行为里。
显式写清楚比让对方猜更稳,正文越本地化结构化数据越要写回国际格式是同一条原则。
把每类页面的取值写死是个好习惯,五类页面的差异化配置是一份现成的表。
不成文的缺省只有一种办法能拿到,就是把它测出来。测的成本不高,本文全部结论用的是每个站四次请求;难的是想到该去测,以及测完把结果写下来变成团队的共同知识。
成文的缺省要显式化,不成文的缺省要基线化。前者花的是查文档的时间,后者花的是搭一套小测试的时间。两件事都不难,难在没人会提醒你这里有个东西需要你做决定。
把这套东西交给别人时该说什么
最后补一段交接的话,因为这类知识最容易死在人员变动上。
该写下来的东西不止技术这一摊,三地区架构与合规开户对照也是要留档的那类。
交接和交付都要有明确定义,达标定义与验收条款怎么写是把它写进合同的做法。
交接时要传的不是那份测试脚本,脚本谁都能重写。要传的是三件事:这个站在哪几个维度上会随请求变化、这些变化分别由哪一层产生、以及上一次核对是什么时候。
第一件决定了接手的人改动时要小心什么。第二件决定了出问题时该找谁。第三件决定了这份信息还能不能信——超过半年没核对过的清单,应该当成过期处理,重新跑一遍再用。
这三句话写下来不超过一页纸,但它把一个只存在于服务器行为里的事实,变成了团队里可以传递的东西。缺省分支之所以难对付,不是因为它复杂,是因为它不留痕迹;而写下来就是留痕迹最便宜的方式。
顺带一句,这份文档最好和代码放在一起,不要放在文档系统里。放在代码库里的东西,改代码的人有机会看见;放在别处的东西,只有想起来找的人才看得见。
常见问题解答
Vary头到底该写什么?
写你的响应确实会随之变化的那些请求头,一个不多一个不少。少写会导致缓存把一个版本发给所有人,多写会把缓存切得太碎、命中率下降。最常见的必写项是 Accept-Encoding,这一项多数服务器会自动补。如果你的站按语言、按设备、按地区返回不同内容,就必须显式加上对应的字段。这次实测的139个站里,写了 Accept-Encoding 的有120个,而写了 Accept-Language 的只有3个,实际随语言变的却有17个。
怎么快速测出自己的站随什么变?
对同一个地址发三次请求:第一次用正常浏览器头,第二次把 Accept-Language 换成目标市场语言,第三次把 Accept-Encoding 整个去掉。比对三次的最终地址、Content-Encoding、页面 lang 属性和正文体积。只要有任何一项不同,而响应里的 Vary 没有对应字段,就是漏声明。三次请求不到五分钟,能覆盖大部分问题。
比对两次响应时,用整页哈希可靠吗?
不可靠,除非你先把页面里的随机字段清干净。本文第一版就是用整页指纹判定的,得出64个站有差异,翻明细才发现一大批是字节数完全相同、只有内部随机串不同的假阳性,收紧判据后真实数字是17个,虚高了3.8倍。更稳妥的做法是只提取几个稳定位置做比对:最终地址、html 标签的 lang、页面标题、正文体积,这几项不会被随机串影响。
首页不写Cache-Control会怎么样?
缓存不会因此不缓存,而是走启发式规则:用 Last-Modified 与当前时间的差值取一个比例当作可缓存时长,各家实现不完全一致。这次样本里有21.6% 的站首页没有这个头,而带 Last-Modified 的只有12.2%,两样都没有的情况下行为最不可预测。首页这类含个性化内容的页面,建议显式写明策略,不要让每一层缓存各自猜一个值。
为什么很多站的首页干脆写no-store?
因为首页上通常有购物车数量、会员状态、个性化推荐这类因人而异的内容,缓存一份发给所有人会出事。样本里 private, no-store 是最常见的取值,36个站在用。这是最安全但也最贵的选择——每次访问都要回源。如果把 Vary 用对,理论上可以做到既缓存又分版本,只是需要先把随什么变这件事想清楚并写下来。
首次请求就被写入七八条Cookie正常吗?
在电商站上属于常态。这次139个站里有119个在第一次首页请求就写了Cookie,条数中位7条,最多的一个写了39条。名单里绝大多数来自建站平台和边缘服务商,比如店铺平台的会话标识、地区与货币记忆、边缘层的机器人识别标记,站主自己写的很少。值得做的是把这份清单列出来逐条确认用途,问不出来的那几条通常来自已经不用了的第三方脚本。
Cookie不写Domain属性有什么影响?
缺省是只作用于发出它的那个主机名,不包含子域。这个方向是安全的,但如果你有多个子域,比如商城和博客分开部署,那么不写这一项的会话标识在两边是不通的,跨子域的行为分析会把同一个人看成两个访客。这次778条Cookie里有537条没写这一项。要跨子域共享就得显式写 Domain,同时注意这会扩大Cookie的发送范围。
不压缩的响应比压缩的大多少?
这次实测的中位数是6.44倍。也就是压缩后200 KB的首页,原始体积在1.3 MB上下。这个倍数可以当参照用:如果日志里某个客户端拉走的字节数是常规的六七倍,多半不是它在攻击你,而是它请求时没带 Accept-Encoding——一些老旧的抓取脚本和简易监控探针就是这样。
权威参考资料
本文标题:《Vary响应头实测:说变的3个,真变的17个》
本文链接:https://zhangwenbao.com/vary-header-declared-vs-actual-response-variation.html
版权声明:本文原创,转载与引用请注明作者与原文链接。许可协议: CC BY 4.0