Vary响应头实测:说变的3个,真变的17个

Vary响应头实测:说变的3个,真变的17个
张文保 74 分钟阅读 1,658 阅读
本文目录
  1. 同一个地址,为什么会给出不一样的东西?
  2. 请求里带着的那几行字
  3. 缓存必须知道这件事
  4. 缓存投毒是怎么发生的
  5. 为什么这条声明特别容易漏
  6. 协商和跳转是两条不同的路
  7. 为什么内容协商用的人越来越少
  8. 说了随什么变的有几个,真的在变的有几个?
  9. 声明侧:92.1% 的站发了Vary,但内容高度集中
  10. 写多了会怎样
  11. 实测侧:17个站确实随语言变
  12. 变化的具体形态
  13. fiskars那个芬兰语版说明了什么
  14. 那3个声明了随语言变的站
  15. 从64收到17,中间那47个是怎么回事?
  16. 第一版判据错在哪
  17. 换成什么判据
  18. 为什么第一版数字看着更像真的
  19. 这件事本身的教训
  20. 虚高的数字为什么总是往同一个方向偏
  21. 压缩这一层的默认协商
  22. 带上压缩偏好时给什么
  23. 不带压缩偏好时给什么
  24. 17个站的编码变了却没声明
  25. 一次真实的排查:慢,但慢得没道理
  26. 一个都没有出现的编码
  27. 压缩比这个数还能用来做什么
  28. 不写Cache-Control的那30个站,缓存怎么办?
  29. 21.6% 的站没有这个头
  30. 写了的那109个站写了什么
  31. ETag有一半的站在发,它派什么用场
  32. 缓存命中率的实际情况
  33. 首页不缓存,不等于整站都不该缓存
  34. Age头能告诉你什么
  35. 首次请求就写下的778条Cookie,是谁写的?
  36. 85.6% 的站在第一次请求就发Cookie
  37. 这些Cookie是干什么的
  38. 为什么这份名单里几乎没有自己写的东西
  39. Domain属性没写的有537条
  40. 安全属性的覆盖率说明了什么
  41. 第三方域的Cookie只有4个站
  42. 七条Cookie意味着每个请求多带多少字节
  43. 同意条弹出之前就已经写好了
  44. 一次响应里36个头字段,你认识几个?
  45. 中位36条,最多235条
  46. 覆盖率最高的那一批
  47. 头字段数量的两端分别是什么站
  48. 边缘层是谁
  49. 64% 的站宣告了下一代协议,这次一次都没用上
  50. 272种字段名里,有多少是给人看的
  51. 安全头那一组的覆盖率为什么高
  52. 这些不成文的缺省,到底是谁定的?
  53. 三个来源,各管一段
  54. 三层各自的可预测性
  55. 为什么第三层最容易漏
  56. 怎么判断一个行为是哪一层来的
  57. 平台层的行为会变,而且不通知你
  58. 这些缺省该怎么测出来?
  59. 测试矩阵怎么搭
  60. 比对什么才算数
  61. 然后和Vary对一遍
  62. 把它做成一次可重复的检查
  63. 用什么工具
  64. 什么时候跑
  65. 结果怎么读才不误判
  66. 一份可以照着跑的实测清单
  67. 五分钟版
  68. 五分钟版能查出什么,查不出什么
  69. 半天版
  70. 顺手能查出来的另外三件事
  71. 做完之后写在哪
  72. 这份清单该由谁维护
  73. 测出来之后,该改哪一层?
  74. 能在源站改就别在边缘改
  75. 如果非要在边缘改
  76. 修复的优先级怎么排
  77. 把语言分发从内容协商换成显式地址
  78. 改完怎么验证
  79. 一个不改代码的过渡方案
  80. 搜索引擎拿到的是哪一版
  81. 回到那条一般规律
  82. 把这套东西交给别人时该说什么
  83. 常见问题解答
  84. Vary头到底该写什么?
  85. 怎么快速测出自己的站随什么变?
  86. 比对两次响应时,用整页哈希可靠吗?
  87. 首页不写Cache-Control会怎么样?
  88. 为什么很多站的首页干脆写no-store?
  89. 首次请求就被写入七八条Cookie正常吗?
  90. Cookie不写Domain属性有什么影响?
  91. 不压缩的响应比压缩的大多少?
  92. 权威参考资料

摘要:给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-Encoding12086.3%
Cookie42.9%
Accept-Language32.2%
User-Agent32.2%
Origin21.4%

压缩那一项独占鳌头,其余全是零头。这和上一节说的机制完全对得上:Accept-Encoding是服务器自动补的,其余得靠人写,而人几乎不写。

另外还有一批值得一提的令牌:6个站写了 RSCNext-Router-State-TreeNext-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的 langen-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之类。这次的第一个变体就是这么发的,结果是:

体积只是性能的一环,六层架构把加载时间压下来的实操是完整路径。

压缩这件事还能在应用层再做一层,免插件压缩与压缩算法叠加是具体写法。

返回的编码站数占比
br9769.8%
gzip4028.8%
不压缩21.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-store36
no-cache, no-store, must-revalidate9
public, max-age=0, must-revalidate12
no-store, no-cache, must-revalidate, max-age=04
no-cache, no-store, max-age=0, must-revalidate3

把这几行读一遍会发现一个共同点:绝大多数都是各种写法的“别缓存”。首页作为一个高流量入口,被明确要求每次都回源。

原因不难理解:首页上有购物车数量、有会员状态、有个性化推荐,缓存一份发给所有人是要出事的。所以大家选了最安全的做法——干脆不缓存。

但这个选择的代价是每一次首页访问都要打到源站。如果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_essential96店铺平台必需会话
localization53记住地区选择
cart_currency52记住货币
_shopify_y / _shopify_s各46访客与会话标识
_shopify_analytics / _marketing各45分析与营销归因
__cf_bm26边缘层机器人识别

这份名单里几乎没有站主自己写的东西。它们全部来自建站平台和边缘服务商,是开店和接入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-Encoding98.6%服务器自动
Server92.8%服务器自动
Vary92.1%服务器自动为主
Strict-Transport-Security90.6%平台或人工
Set-Cookie85.6%平台自动
X-Content-Type-Options80.6%平台或人工
Cache-Control78.4%需要人工
Content-Security-Policy67.6%需要人工
ETag51.8%服务器自动
Last-Modified12.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 标签的属性、titlemeta 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-ControlLast-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

继续阅读
发表评论
分享到微信 或在下方手动填写
支持 Ctrl + Enter 提交