每个请求都把同一份Cookie重发一遍,HTTP/2本可以只发一次却没做到
本文目录
- 一个请求头到底有多重?
- HTTP/1.1把同一份头重发了多少遍?
- HTTP/2说它解决了这件事,实际省了多少?
- 只发一个请求的时候,HTTP/2反而更大
- 为什么到了3KB,这笔红利突然归零?
- 那条线是4096除以4乘以3
- 过了线之后,请求发得再多也追不回来
- 把Cookie拆开发,为什么能救回来?
- 这不是钻空子,是规范明写的做法
- 那拆分是不是万能的?总预算又在哪儿?
- 现网的Cookie,离这条4KB的线还有多远?
- 然后用真实浏览器打开了同一批站
- 这次失手值得单独记一笔
- 你的测速工具,站在哪一侧?
- 换算成时间,这笔开销到底是多少?
- 日志里那个字段,两种协议下不能横向比
- 还有一个更别扭的:两种协议连墙的位置都不一样
- 这笔账该怎么省?
- 先花十分钟测出自己站的位置
- 先搞清楚是谁在占地方
- 四个动作,按性价比排
- 要不要为这件事专门上HTTP/2或HTTP/3?
- 常见问题解答
- HTTP/3是不是就没这个问题了?
- 拆成多个Cookie头字段,后端应用需要改代码吗?
- 怎么才能让浏览器按拆分的方式发Cookie?
- 这会影响Core Web Vitals或者排名吗?
- Cookie已经很小了,还有必要看这个吗?
- 为什么不干脆把动态表调大?
- 权威参考资料
摘要:同一份Cookie在一条连接上连发50个请求,HTTP/1.1发出去104KB,HTTP/2只发了2.5KB,差42倍。但把Cookie一点点加大之后,这笔红利在某个位置突然消失:头长2553字节时还省12.46倍,到3064字节只剩1.43倍,此后再也不降。分界线是HPACK动态表那4096字节。更反常的是,把同样这堆Cookie拆成两个头字段发出去,上行立刻从22079字节掉回2505字节,而规范里白纸黑字允许这么干。真实电商站早就越过了这条线:某化妆品站在Chrome里有41条、4176字节,还没算HttpOnly。
上一篇量的是一道墙:Cookie攒到多大,服务器会在应用代码跑起来之前把请求扔掉。现网中位数落在16KB附近,而多数站的用户一辈子也攒不到那么多。
所以那篇文章有个没说完的地方:如果我离那道墙还很远,是不是就没事了?
不是。墙那笔账是二元的——要么撞上,要么什么也没发生。而这些字节还在花另一笔钱,那笔钱是连续的、每天都在付、且从来不报警。这篇要算的就是它。
一个请求头到底有多重?
先建立量级感。保哥拿两种客户端形态,各发一个请求到自己的实验台上,让上游把收到的请求头逐条统计出来。
第一种是真实Chrome会发的那一套:User-Agent、Accept、Accept-Language、Accept-Encoding、Sec-Ch-Ua三兄弟、Sec-Fetch四兄弟、Upgrade-Insecure-Requests,再加一份1500字节的Cookie。第二种是Googlebot那种极简形态:只有UA、Accept、Accept-Encoding,不带Cookie。
| 客户端形态 | 请求头条数 | 请求头字节数 |
|---|---|---|
| 真实Chrome(含Cookie) | 14 | 2115 |
| Googlebot那种极简形态 | 4 | 240 |
8.8倍。而且这个差距是结构性的:Googlebot不存Cookie,它第一万次访问带的东西和第一次一模一样;真实用户则相反,逛得越多带得越多。
2115字节听起来不大,一张缩略图都不止这个数。但请求头有一个和图片完全不同的性质:图片下载一次就被缓存了,请求头是每一个请求都要重新发一遍的。一个中等复杂度的电商页面加载时会发出几十到上百个请求——HTML、CSS、JS、字体、商品图、埋点、接口——而其中每一个同域请求,都要把这2115字节原样再背一遍。
HTTP/1.1把同一份头重发了多少遍?
这个问题没法靠估算,得实测。保哥在服务器上写了个裸TCP中转,架在客户端和nginx中间,只做一件事:数客户端发往服务器方向的字节数,不解析协议、不看内容。这样量到的就是真实上行流量。
然后带上一份2042字节的Cookie(值全是随机十六进制,避免压缩作弊),在同一条连接上连发N个请求:
| 请求数 | HTTP/1.1上行总字节 | 每请求均摊 |
|---|---|---|
| 1 | 2136 | 2136 |
| 2 | 4272 | 2136 |
| 5 | 10680 | 2136 |
| 10 | 21361 | 2136 |
| 20 | 42731 | 2136 |
| 50 | 106841 | 2136 |
均摊值一个字节都没变过。HTTP/1.1对请求头不做任何压缩,也不做任何复用——第50个请求发的那份Cookie,和第1个请求发的那份逐字节相同,服务器早就知道了,客户端还是老老实实又抄了一遍。
50个请求发出去 104KB 的上行数据,其中99%是同一句话说了50遍。这个比喻不太优雅,但确实像极了那种每次开口都要先自报家门的场合。
HTTP/2说它解决了这件事,实际省了多少?
HTTP/2的头部压缩机制叫HPACK,核心是一张动态表:双方各维护一份,发过一次的头字段进表,之后再发同一个字段就只发一个索引号。
同一份Cookie、同一条连接、同样的请求数,换成HTTP/2:
| 请求数 | HTTP/1.1 | HTTP/2 | HTTP/2每请求均摊 |
|---|---|---|---|
| 1 | 2136 | 1567 | 1567 |
| 2 | 4272 | 1589 | 794 |
| 5 | 10680 | 1655 | 331 |
| 10 | 21361 | 1766 | 176 |
| 20 | 42731 | 1996 | 99 |
| 50 | 106841 | 2686 | 53 |
50个请求,HTTP/2一共只发了2686字节,比HTTP/1.1少了42倍。第一个请求要1567字节把内容送过去,后面49个请求每个只花了20多字节——因为它们发的不是Cookie,是“用你表里第几号那条”。
只发一个请求的时候,HTTP/2反而更大
有个细节值得单独拎出来。把Cookie去掉、只发一个请求,两种协议的上行是这样的:
| 场景 | HTTP/1.1 | HTTP/2 |
|---|---|---|
| 无Cookie,1个请求 | 84 | 119 |
| 无Cookie,10个请求 | 841 | 309 |
| Cookie 500字节,1个请求 | 592 | 435 |
第一行是反的:什么都不带、只发一个请求时,HTTP/2比HTTP/1.1多花了35字节。因为它要先发连接前言和SETTINGS帧,把双方的参数对齐。这笔开销是一次性的,第二个请求开始就赚回来了——十个请求就变成省2.7倍。
这个细节的意义在于:HPACK的收益完全来自复用,而复用需要连接活得够久。如果你的站因为某些原因连接频繁被掐断(比如上一篇讲的那个被拒时的Connection: close),那么每次重连都要重新交学费,动态表也要从空的开始重建。
把请求数拉到真实页面的量级,差距更直观。带一份1531字节的Cookie:
| 一个页面发出的请求数 | HTTP/1.1上行 | HTTP/2上行 | 差距 |
|---|---|---|---|
| 1 | 1625 | 1205 | 1.3倍 |
| 20 | 32511 | 1634 | 19.9倍 |
| 50 | 81291 | 2324 | 35.0倍 |
| 100 | 162592 | 3475 | 46.8倍 |
一个发100个请求的页面,光是请求头这一项,HTTP/1.1要上传 159KB。这个数字值得停一下:它已经超过很多站首页HTML压缩后的体积了。而它跑在上行方向——家庭宽带、移动网络的上行带宽普遍只有下行的几分之一。页面体积那笔账大家算得很熟,算的都是下行;上行这一侧几乎没人算过。
为什么到了3KB,这笔红利突然归零?
上面的实验里Cookie是2042字节。保哥想知道Cookie更大时能省多少,于是把它一档一档加上去,每档都跑10个请求:
| Cookie头实际长度 | HTTP/1.1上行 | HTTP/2上行 | 倍数 |
|---|---|---|---|
| 1531 | 16251 | 1401 | 11.60 |
| 2042 | 21361 | 1767 | 12.09 |
| 2553 | 26471 | 2124 | 12.46 |
| 3064 | 31581 | 22009 | 1.43 |
| 3648 | 37421 | 26189 | 1.43 |
| 4232 | 43261 | 30259 | 1.43 |
| 5035 | 51291 | 35969 | 1.43 |
2553到3064之间发生了一次跳水:12.46倍变成1.43倍,而且之后再也没变过。那个恒定的1.43是Huffman编码贡献的——HPACK对字符串本身还有一层无状态压缩,这部分永远在,但它跟“发过就不用再发”完全是两回事。
把步长缩到50字节再扫一遍,分界卡得非常死:
| Cookie头长度 | HTTP/2每请求均摊 | 索引复用 |
|---|---|---|
| 2918 | 238 | 正常 |
| 2991 | 243 | 正常 |
| 3064 | 2198 | 失效 |
| 3137 | 2250 | 失效 |
2991和3064之间,均摊值涨了9倍。这个位置不是随便来的。
那条线是4096除以4乘以3
HPACK动态表的容量在 HTTP/2规范定义SETTINGS参数的那一节里写死了初始值:
When a connection is established, the dynamic table size for the HPACK decoder and encoder at both endpoints starts at 4,096 bytes, the initial value of the SETTINGS_HEADER_TABLE_SIZE setting.
而一个表项占多少空间,HPACK规范里算表大小的那一节给了公式:
The size of an entry is the sum of its name's length in octets (as defined in Section 5.2), its value's length in octets, and 32.
套进去算:字段名 cookie 是6字节,加上32字节固定开销,所以2991字节的Cookie占3029,3064字节的占3102。分界正好落在3072,也就是4096的四分之三。编码器不愿意让一个大到这个程度的条目挤进表——它一进去,表里别的东西就全被顶出去了,得不偿失。
需要说明的是,“四分之三”这个具体比例是各家HTTP/2实现自己的取舍策略,规范只规定了表的总容量和条目怎么算大小,没规定超过多少就不索引。但方向是一致的:表就那么大,一个条目吃掉大半张表,索引就没意义了。
过了线之后,请求发得再多也追不回来
红利消失后是不是发得多还能摊薄一点?保哥在悬崖两侧各测了一组:
| 请求数 | Cookie 3064字节的均摊 | Cookie 4232字节的均摊 |
|---|---|---|
| 1 | 2287 | 3123 |
| 5 | 2208 | 3044 |
| 20 | 2194 | 3030 |
| 50 | 2191 | 3027 |
从第一个请求到第五十个,均摊值几乎是一条水平线。对比前面那张表——Cookie小一点的时候,均摊从1567一路掉到53。过了这条线,HTTP/2在请求头这件事上就退化成了一个带Huffman编码的HTTP/1.1。你升级了协议、改了配置、换了CDN,该重发还是重发。
把Cookie拆开发,为什么能救回来?
这是这轮实验里最让保哥意外的一个结果。
取一份3064字节的Cookie,跑10个请求,上行22009字节——已经过了悬崖。然后把完全相同的内容拆成两个 Cookie 头字段,各1531字节,其他什么都不改:
| 写法 | 内容 | 10个请求的上行 |
|---|---|---|
| 合并成一个头字段 | 3064字节 | 22079 |
| 拆成两个头字段 | 各1531字节 | 2505 |
8.8倍。同样的Cookie、同样的总字节、同一个服务器、同一个协议,只是换了个分行方式。服务端收到的东西也完全一样——上游PHP打出来的 COOKIELEN 两种写法分毫不差。
这不是钻空子,是规范明写的做法
HTTP/2规范里专门讲Cookie头压缩的那一节,把这件事的来龙去脉说得很完整。先是解释了问题出在哪:
This can significantly reduce compression efficiency, as updates to individual cookie-pairs would invalidate any field lines that are stored in the HPACK table.
因为Cookie用分号分隔而不是逗号,按HTTP的规矩它没法拆成多行,所以全站几十条Cookie只能挤成一行。而这一行里任何一个键值对变了,整行的索引就作废了——用户点了个筛选、埋点更新了一下时间戳,这一整行3KB就得重新发一遍。
然后规范直接给了解法:
To allow for better compression efficiency, the Cookie header field MAY be split into separate header fields, each with one or more cookie-pairs. If there are multiple Cookie header fields after decompression, these MUST be concatenated into a single octet string using the two-octet delimiter of 0x3b, 0x20 (the ASCII string "; ") before being passed into a non-HTTP/2 context.
规范甚至贴心地举了例子,说明 cookie: a=b; c=d; e=f 和分成三行写语义完全等价。接收端必须自己拼回去,所以你的应用代码一个字都不用改。
规范说的是“更好的压缩效率”,但没给数字。上面那个8.8倍,就是这句话在真实链路上的价格。
那拆分是不是万能的?总预算又在哪儿?
不是万能的,而且第二道线来得比想象中快。保哥把Cookie一律拆成每条1020字节,然后一条一条往上加:
| 拆成几个头字段 | 总字节 | 10个请求的上行 | 每请求均摊 |
|---|---|---|---|
| 1 | 1020 | 1043 | 104 |
| 2 | 2040 | 1776 | 177 |
| 3 | 3060 | 2509 | 250 |
| 4 | 4080 | 29514 | 2951 |
| 5 | 5100 | 36774 | 3677 |
| 6 | 6120 | 44004 | 4400 |
三条还好好的,第四条一加上去就整个崩了。换个方向再验一次,这回每条只有200字节、靠条数堆总量:
| 条数 | 总字节 | 每请求均摊 |
|---|---|---|
| 10 | 2060 | 195 |
| 12 | 2472 | 229 |
| 16 | 3296 | 2555 |
| 20 | 4120 | 3178 |
再补一发最干净的对照:单独一条2900字节的Cookie,10个请求上行2387字节,红利在;把它复制成两条、总量5800字节,同样10个请求上行 41964 字节,全崩。
结论就很清楚了:拆分能绕开“单条太大”这道线,但绕不开那张表本身的容量。动态表一共4096字节,里面还要装UA、Accept这些别的头。所有想被索引的东西加起来一旦装不下,就会陷入不停插入、不停驱逐的循环,红利归零。
换句话说,HTTP/2给请求头的复用额度大约是4KB,这是一个总预算,不是每条的限额。拆分是让你在预算内花得更聪明,不是让你多拿预算。
现网的Cookie,离这条4KB的线还有多远?
这一节保哥要先承认一次测量失手,因为这个失手比结论本身更有用。
第一轮测量用的是一个不执行JavaScript的HTTP客户端,带Cookie罐子,逛125个站的首页加三个内页,每一步记录“下次请求会带多长的Cookie”:
| 逛到第几页 | Cookie头字节中位数 | p90 | 最大值 |
|---|---|---|---|
| 第1页 | 0 | 390 | 2965 |
| 第2页 | 28.5 | 400 | 2973 |
| 第3页 | 28.5 | 438 | 2981 |
| 第4页 | 30 | 400 | 2993 |
逛完四页比首页多出来的字节,中位数是0。首页Cookie超过1KB的站只有7个,占5.7%。
照这组数字,本文前面讲的那条4KB线基本就是个理论问题——现网离得远着呢。保哥当时差点就这么写了。
然后用真实浏览器打开了同一批站
保哥打开Chrome,访问sephora.com,等页面完全加载完,读了一下 document.cookie:
| 同一个站,同一时段 | Cookie条数 | 字节数 |
|---|---|---|
| 不执行JS的HTTP客户端 | 20 | 2513 |
真实Chrome(document.cookie) | 41 | 4176 |
而 document.cookie 读不到HttpOnly那部分——真实发出去的Cookie头只会比4176更长。另外两个站也是同一个方向:hubspot.com的HTTP客户端量到0字节,真实浏览器里有6条268字节。
差距全部来自JavaScript。Set-Cookie响应头只是冰山露出水面的那一角,统计脚本、广告像素、A/B分桶、客服挂件、同意管理平台,这些全是页面加载完之后由脚本写进去的,不跑JS就一条都看不见。
把两组数据合起来看,事情就成立了:那个4176字节已经越过了4096这条线,而它甚至还没算HttpOnly。这个站的每一个同域请求,在HTTP/2上都拿不到任何索引复用,等于白升级了一次协议。
这次失手值得单独记一笔
教训不是“我用错了工具”,而是一个测量方法的盲区,正好和被测对象的主要来源重合了。不执行JS的客户端看不到JS写的Cookie,而JS写的Cookie恰恰是现代网站里增长最快的那一部分。如果只跑第一轮就下结论,得到的会是一个方向完全相反的答案——不是“偏差了几个百分点”,是“没事”和“早就超了”。
这和只用干净浏览器测试却测不出老用户故障,其实是同一个错误的两个版本:你手里那个客户端像不像真实用户,决定了你能不能看见这件事。
你的测速工具,站在哪一侧?
把前面的数字摆在一起,会得到一个不太舒服的画面。
| 客户端 | 请求头字节 | 能否触发HPACK失效 | 它测到的速度代表谁 |
|---|---|---|---|
| Googlebot | 240 | 永远不会 | 不代表任何真实用户 |
| SEO爬虫工具 | 几百 | 默认不会 | 同上 |
| 实验室性能测量 | 干净环境 | 不会 | 首次访问的新用户 |
| 逛了半年的老客户 | 2000到5000+ | 每次都触发 | 你最值钱的那批人 |
Googlebot那240字节的请求头意味着,它不但撞不到上一篇讲的那道墙,也永远吃不到这一篇讲的那笔亏。它对你站点上行开销的体验,和真实老用户完全不是一回事。所以抓取统计里的响应时间、爬虫工具跑出来的速度评分,测的都是一个从不带Cookie的客户端眼中的世界。抓取预算那套算法里,服务器响应速度是个明确的输入变量,而它读到的正是这个偏乐观的数。
顺带说个已经被反复验证的现象:Googlebot本来就不怎么理会你给浏览器准备的那些提速手段,它对资源提示的处理方式和真实浏览器差别很大。请求头这一层只是又多了一条:它连“重复发送”这个问题都不存在。
换算成时间,这笔开销到底是多少?
字节要变成体感,得看它花掉多少时间。保哥先在最干净的条件下量了一个下界:客户端和服务器在同一台机器上,网络延迟基本为零,纯粹测服务器解析这些头要多久。每档发10次取平均:
| Cookie长度 | 零延迟环境下的首字节时间 | 相对增幅 |
|---|---|---|
| 0字节 | 1.37毫秒 | 基准 |
| 2000字节 | 1.44毫秒 | +5% |
| 6000字节 | 1.65毫秒 | +20% |
这0.28毫秒是纯解析开销,不含任何传输时间——本机回环上字节几乎是瞬间到达的。真实链路上还要叠加两件事:一是这些字节要真的在网线上跑一趟,上行带宽越窄花得越久;二是请求头一旦超过一个数据包能装下的量,就要拆成多个包发,而在高延迟链路上多一个来回就是几十上百毫秒。
所以这个+20%是下界,不是上界。对同城光纤用户,它可以忽略;对跨洋访问、对移动网络下的用户,它是实打实的。而这两类用户恰恰是出海独立站的主力。
日志里那个字段,两种协议下不能横向比
想自己量一量的话,nginx有个 $request_length 变量,看起来正合适。但它有个坑。保哥用同一份2042字节的Cookie,分别用两种协议各发一次,看日志:
| 协议 | 日志记录的request_length | 中转层数到的真实上行 |
|---|---|---|
| HTTP/1.1 | 2137 | 2136 |
| HTTP/2 | 1487 | 1567 |
同一份请求头,日志里的数字差了30%。因为HTTP/2记的是压缩之后的量。所以拿这个字段做统计时,混着两种协议的流量算出来的平均值没有意义——你会误以为升级HTTP/2之后请求变小了30%,而真实的节省要么是42倍,要么是0,取决于Cookie有没有过线。日志字段该怎么选、怎么才不会算歪,访问日志格式那一篇里有更系统的取舍。
还有一个更别扭的:两种协议连墙的位置都不一样
上一篇量的那道墙,在HTTP/2上根本不在同一个位置。同一台nginx、同一份 large_client_header_buffers 8 32k 配置、同一个33000字节的Cookie:
| Cookie长度 | HTTP/1.1 | HTTP/2 |
|---|---|---|
| 24000字节 | 200 | 200 |
| 33000字节 | 400 | 200 |
| 40000字节 | 400 | 200 |
换成完全随机的Cookie值再跑一遍,结果一样,说明和压缩率无关——两种协议的头部解析走的本来就是两条路。
这带来一个很难查的故障形态:同一个用户,协商成HTTP/2时一切正常,某次降级回HTTP/1.1就打不开了。而降级在弱网、老旧中间设备、部分企业代理后面时有发生。用户的描述会是“有时候能开有时候不能开”,这句话在排查清单里通常被归到网络抖动,很少有人想到是协议协商的结果不同。
这笔账该怎么省?
先花十分钟测出自己站的位置
需要知道的只有一个数:真实用户在你站上的Cookie头有多长。不是你以为的,是真实浏览器里的。打开Chrome访问自己的站,正常逛三五个页面,然后在控制台跑一行:
document.cookie.length再打开开发者工具的网络面板,随便点一个同域请求,看请求头里Cookie那一行的实际长度(这个值包含了HttpOnly,比上面那个准)。对照下面这张表定位:
| Cookie头长度 | HTTP/2状态 | 优先动作 |
|---|---|---|
| 1KB以内 | 红利完整 | 保持住,别让第三方脚本乱写 |
| 1到2.5KB | 红利完整但接近边缘 | 盘一遍谁在写,设个红线 |
| 2.5到4KB | 大概率已失效 | 拆分头字段能立刻见效 |
| 4KB以上 | 确定失效 | 必须先减肥,拆分救不回来 |
先搞清楚是谁在占地方
减肥之前得先知道肥在哪儿。保哥把这轮抓到的241条真实Cookie拆开看了看值的长度:
| 指标 | 数值 | 说明 |
|---|---|---|
| 值长度中位数 | 36字节 | 一半的Cookie其实很小 |
| 值长度p90 | 477字节 | 头部10%开始变重 |
| 值长度最大值 | 1507字节 | 一条顶掉四十条 |
| domain以点开头 | 172条(71.4%) | 所有子域请求都要带 |
| 过期时间超过一年 | 14.5% | 装上就基本不会走 |
这张表指出了一个很实用的策略:Cookie的体积分布是极度不均的,中位数才36字节,而最大的那条有1507字节。所以减肥不该从“我要不要少存几条”开始,而应该先把最大的三五条揪出来——它们通常是被塞进去的JSON、序列化后的用户偏好、或者某个SDK的整包状态。干掉一条1507字节的,比删掉四十条小的还管用。
那个71.4%同样值得注意。写在点开头domain上的Cookie,主站、静态资源子域、接口子域的每一个请求都要带一遍。如果你的图片走的是自家子域而不是独立域名,这些Cookie就跟着每一张图片发了一次——一个有50张图的商品页,就是50份。
四个动作,按性价比排
- 把大对象从Cookie里搬走。购物车快照、用户偏好、浏览历史这些应该放服务端会话或localStorage,Cookie里只留一个ID。这条通常一次就能砍掉一多半,而且同时解决墙和预算两个问题。
- 静态资源换一个不带Cookie的域名。一个页面里请求数最多的就是图片、CSS、JS,而它们根本不需要认识用户。这一条能把“每请求都背一遍”里的大部分请求直接摘出去——前面那张100个请求的表,如果其中80个走了独立域名,账立刻就变了。
- 收窄Cookie的作用域。保哥这轮抓到的241条真实Cookie里,172条(71.4%)的domain是点开头的,也就是所有子域都要带。能限定到具体子域和路径的就别写在顶级域上。作用域这件事的连带影响不止一处,Cookie作用域那篇里有完整的边界拆解。
- 确认边缘层没把拆分的头字段合并掉。这条是新的:如果你按规范拆成了多个Cookie字段,而链路上某一层在转发时把它们拼成了一行再发给下一跳,那么后半段链路的红利就没了。判据是在最终落地的那台服务器上看它收到几个字段。
要不要为这件事专门上HTTP/2或HTTP/3?
如果还在HTTP/1.1上,那答案很简单:上,而且这只是顺带的好处之一。反向代理那篇里的HTTP/2配置段可以直接抄。
但如果已经在HTTP/2上,那么这笔红利你可能根本没拿到,而账单上不会显示这一项。它不会报错、不会变慢到被投诉、不会出现在任何一张监控图上——只是每个请求多传两三千字节,在上行方向,对着你最忠实的那批用户。TTFB优化那一篇讲的是服务器处理时间这一侧,请求还没到服务器之前的这一段,是它没覆盖到的地方。
保哥顺手量了一下本站:首访不下发任何Cookie,一条Set-Cookie都没有。这是内容站的便宜——没登录、没购物车、没挂几个第三方脚本,Cookie想长也长不起来。但也正因如此,本站压根测不出这个问题,前面那些数字全是在实验台和别人的站上量出来的。这一点和上一篇的结论刚好接上:你能不能看见一个问题,取决于你手里那个客户端像不像真实用户。
常见问题解答
HTTP/3是不是就没这个问题了?
机制换了,但预算的逻辑还在。HTTP/3用的头部压缩叫QPACK,是为QUIC的乱序特性重新设计的,同样有一张容量有限的动态表,同样是“发过的进表、之后发索引”。所以“表就那么大、大条目挤不进去”这个约束依然成立,只是具体的容量和策略由实现和配置决定。把Cookie压小这个动作,在HTTP/1.1、HTTP/2、HTTP/3上都是正收益,不存在白做的情况。
拆成多个Cookie头字段,后端应用需要改代码吗?
不需要。规范明确要求接收端在把请求交给非HTTP/2环境之前,必须用 "; " 把多个Cookie字段拼回成一个字符串。实验台上验证过:拆成四个字段发出去,上游PHP读到的 $_SERVER['HTTP_COOKIE'] 和合并成一条发出去时长度完全一样,条数也一样。拆分这件事只发生在传输层,应用层看到的东西没有任何变化。
怎么才能让浏览器按拆分的方式发Cookie?
这件事的主动权其实不在你手上——拆不拆由客户端的HTTP/2实现决定,网站没有开关能控制浏览器怎么编码请求头。所以这一节的实用价值分两种情况:你自己控制的客户端(App内的HTTP库、服务端之间的调用、爬虫、SDK)可以直接受益;而对普通浏览器流量,真正能做的还是把Cookie压到2.5KB以内,让它天然待在红利区。把这个机制搞清楚的价值在于知道那条线在哪,而不是去改浏览器。
这会影响Core Web Vitals或者排名吗?
直接影响很小,间接影响看场景。上行几千字节在光纤上是微秒级的事,但在上行带宽受限的移动网络、或者跨洋高延迟链路上,多出来的字节要占用更多的传输轮次,会体现在首字节时间上。更重要的是它的分布特征:受影响的是老用户,而实验室测量测的是新用户,所以你会看到现场数据和实验室数据对不上,却找不到原因——这种对不上该怎么读,Core Web Vitals那笔投入产出账里有更完整的判据。至于排名,Googlebot那240字节的请求头决定了它感受不到这件事,所以不存在直接的抓取或排名惩罚。
Cookie已经很小了,还有必要看这个吗?
有必要看一眼,但优先级不高。判据很简单:真实浏览器里Cookie头在1KB以内,这件事对你就是解决了的,做别的优化收益更大。要注意的是它会自己长——每装一个新的第三方脚本、每上一个A/B测试工具,都可能悄悄加几百字节。建议的做法是把它列进上线检查清单,装新脚本前后各量一次,别等到过线才发现。
为什么不干脆把动态表调大?
因为这个值是接收方声明的,服务器可以通过SETTINGS告诉客户端自己愿意用多大的表来解码。理论上调大能让更大的条目进表,但代价是每条连接的内存占用上升,高并发下不划算,而且客户端那边也有自己的上限。更根本的问题是:调表只是让你能塞下更多冗余,而那些字节仍然要在第一次完整传输一遍,并且在任何一个cookie-pair变化时全部重来。把Cookie减肥是治本,调表是给病灶换个更大的房间。
权威参考资料
本文标题:《每个请求都把同一份Cookie重发一遍,HTTP/2本可以只发一次却没做到》
本文链接:https://zhangwenbao.com/http2-hpack-cookie-request-header-bytes-seo.html
版权声明:本文原创,转载与引用请注明作者与原文链接。许可协议: CC BY 4.0