很多人天天跟 HTTP 打交道,接口通了就万事大吉,一报错就只会看状态码是不是 200。但真要把 HTTP 协议从头到尾捋一遍,问几个"为什么",能讲清楚的人并不多。这篇文章就是一次完整的 HTTP 总结,从协议演进、报文结构、请求方法、状态码、连接机制、缓存,再到 HTTPS 和版本升级,全部串起来讲一遍。既适合刚接触前后端开发的新手建立整体认知,也适合有一定经验但知识点比较零散的老手查漏补缺。读完你会发现,很多之前踩过的坑,其实协议早就把原因写在明面上了。
1. HTTP 协议的演进脉络:每一代解决了什么问题
1.1 从 0.9 到 1.1:互联网初期的协议成形
HTTP 协议诞生之初远比现在简单。最早的 0.9 版本只有一个 GET 请求,没有请求头,没有状态码,服务器返回一段 HTML 就断开连接。那个年代的 Web 就是"纯文档浏览",所以协议只要做到"把内容取回来"就够了。
到了 1.0 版本,请求头和状态码出现了,Content-Type 也开始支持不同类型的内容传输。但 1.0 有一个明显的毛病:每个请求都要新建一次 TCP 连接,用完即断。一个网页如果引了 20 张图片,浏览器就得建立 20 次 TCP 连接。TCP 的三次握手开销加上慢启动过程,直接让页面加载慢得离谱。
HTTP/1.1 解决了这个问题,核心手段就是默认开启持久连接。同一个 TCP 连接上可以连续发送多个请求,不用每次都重新握手。同时 1.1 还加入了 Host 头,允许一台服务器用不同域名跑多个网站,也就是虚拟主机。还有分块传输编码和缓存控制指令,这些基础能力一直沿用到今天。
记住一个核心结论:HTTP/1.1 是目前兼容性最好、使用范围最广的版本,你日常调试的绝大多数请求都是这个版本。
1.2 HTTP/2:二进制分帧与多路复用带来的变化
HTTP/1.1 虽然有了持久连接,但它在同一时刻只能串行处理请求。一个连接上发了一个请求,必须等响应回来才能发下一个。所以浏览器为了加快加载速度,只能同时打开多个 TCP 连接,通常一个域名下会开 6 个左右,这就是人们常说的"浏览器并发连接数限制"。
HTTP/2 的出现改变了这个局面。它把数据拆成更细的帧,并且在同一个 TCP 连接上通过"流"来并发传输多个请求响应。这就是所谓的二进制分帧和多路复用。简单理解就是:一条高速公路上画了多个车道,不同请求的车可以并排跑,不再需要排队等单车道。
除了多路复用,HTTP/2 还做了头部压缩。HTTP 请求里的 Header 其实非常重复,尤其 Cookie 这类字段动不动就几百字节。HTTP/2 用 HPACK 算法把重复的头部信息压缩掉,实测下来头部体积能减少一大半。
HTTP/2 的另一个特性是 Server Push,服务器可以在客户端请求之前主动推送资源。这个设计想法很好,但实际使用中因为缓存命中率难以预测,很多项目并没有大规模采用。
1.3 HTTP/3:绕过 TCP 队头阻塞的尝试
HTTP/2 的多路复用解决了 HTTP 层的队头阻塞,但底层的 TCP 仍然存在队头阻塞问题。什么是 TCP 队头阻塞?简单说就是:一个 TCP 连接里传输的包如果丢了,后续的包即使已经到了,也必须等丢失的包重传成功才能交给上层处理。
这在弱网环境下特别明显。HTTP/2 把多个请求都塞进同一条 TCP 连接,一旦某个包丢失,所有请求都得跟着等,反而比 HTTP/1.1 的表现还差。
HTTP/3 的思路是彻底甩开 TCP,改用基于 UDP 的 QUIC 协议。QUIC 自己实现了可靠传输、拥塞控制、连接迁移等功能,丢包时只影响单个流,不会拖累其他流。同时它把 TLS 加密直接集成到协议内部,握手只需要 1 个往返,连接建立更快。
不过 HTTP/3 目前主要是在对性能要求极高的场景下使用,比如视频会议、直播、弱网环境。日常业务系统如果还没有特殊需要,HTTP/2 仍然是性价比最高的选择。
2. 报文结构与常见 Header:看完就能读懂抓包数据
2.1 请求报文与响应报文的完整结构
HTTP 报文的结构其实非常固定,不管请求还是响应,都是"起始行 + Header 区 + 空行 + 消息体"。下面用一个实际的请求来拆解。
一个典型的 POST 请求长这样:
POST /api/login HTTP/1.1 Host: example.com Content-Type: application/json Content-Length: 42 User-Agent: Mozilla/5.0 Accept: application/json {"username":"admin","password":"123456"}第一行是请求行,包含请求方法、路径和协议版本。从第二行开始到空行之前,全是请求头。空行之后才是请求体。注意空行是必须的,很多初学者做底层 Socket 解析时不处理这个空行,最后发现读出来的数据差一行,其实问题就出在这里。
响应报文的结构类似,第一行是状态行,内容大概是这样的:
HTTP/1.1 200 OK Content-Type: application/json Content-Length: 27 Date: Wed, 16 Nov 2024 10:00:00 GMT {"code":0,"message":"ok"}状态行包含协议版本、状态码和状态描述。状态码后面那段英文描述其实只是为了让人看懂,程序判断时基本只看数字。
2.2 需要重点掌握的头部字段
Header 里面有很多字段,我不打算把整个 RFC 都背一遍,只说日常开发中最常用、最容易出问题的几个。
Host 字段代表目标域名和端口。如果你在写反向代理或者做虚拟主机,这个字段极其重要。多个域名共用一台服务器时,后端必须靠 Host 头来区分到底要路由到哪个服务。
Content-Type 和 Content-Length 这两个字段是一对。Content-Type 告诉接收方消息体的格式,是 JSON 还是表单还是纯文本。Content-Length 告诉接收方消息体到底有多长。接收方读完 Content-Length 指定的字节数就算读完一条消息,准备读下一条。这两个字段如果对不上,轻则解析失败,重则引发请求走私类的安全漏洞。
Transfer-Encoding: chunked 和 Content-Length 是互斥的。当服务器流式返回数据、无法提前算出总长度时,就会用 chunked 编码。它把数据分成多个块,每块开头是十六进制长度,块结束后用 0 结尾。理解了这个机制,你就知道为什么有些接口能在"还没算完"的时候就开始返回一部分数据了。
还有一类常见的字段是 Accept 系列,比如 Accept-Encoding 表示客户端支持哪种压缩格式,常见的有 gzip、br。服务器按照这个字段决定是否压缩、用哪种算法压缩。线上抓包时如果你发现响应体是一堆乱码,先检查一下是不是把 Accept-Encoding 去掉了,浏览器一般会自动带上它,而 curl 默认并不带。
Cookie 字段也值得一提。HTTP 是无状态协议,服务器怎么记住"你是谁"?靠的就是 Cookie。客户端每次请求都会把本地存储的 Cookie 放在 Header 里传给服务器,服务器通过会话机制反查用户状态。实际开发中要注意 Cookie 的传输开销,它每次请求都会跟着走,Cookie 太大对请求性能的影响非常直接。
3. 请求方法与幂等性:动词背后的语义
3.1 常用方法到底该怎么选
HTTP 方法有很多,但日常开发里真正高频使用的就 GET、POST、PUT、DELETE、PATCH 这几种。很多人选方法全凭习惯,用的最多的就是 GET 和 POST,剩下的一律不碰。这种用法不能说错,但会丢失很多语义上的好处。
GET 的语义是"获取资源",它是幂等的,并且不应该有副作用。什么叫幂等?同一个请求执行多少次,结果都是一样的。GET 请求可以被浏览器缓存,可以被收藏到书签。正因为有这些特性,GET 请求不应该用来做修改数据这种操作。
POST 的语义是"提交数据给服务器处理",它不幂等,同一个 POST 请求发两次可能产生两条订单记录,这也是为什么要做防重提交。
PUT 的语义是"把资源完整地替换为请求体",它是幂等的。比如你要更新一整条用户信息,用 PUT 比较合适。PATCH 则是"对资源做部分修改",它不要求幂等。DELETE 自然是删除资源,也是幂等的。
方法选得好,对做接口设计和文档编写都很有帮助。对方看到你用的是 PUT,就能猜到这是全量更新;看到 PATCH,就能知道这是局部修改。语义清晰能减少大量的沟通成本。
3.2 幂等性对超时重试的影响
幂等性这个话题最容易在"接口超时重试"时暴露问题。设想一个场景:客户端发起支付请求,网络抖动导致响应超时,客户端立刻重试了一次。如果这个接口用的是 POST,两次请求就可能导致两笔支付,这是生产事故级别的bug。
正确的做法是:对这类非幂等操作,要么用 POST 但带上全局唯一的请求 ID,服务器记录这个 ID,重复请求直接返回第一次的结果;要么把接口设计成幂等的,比如下单前先创建一个订单号,拿订单号去操作,同一个订单号重复操作不会产生新订单。
抓包排查超时问题的时候也别忘了这一层。如果看到客户端自动重发请求,服务端还是同样的处理逻辑,那问题很可能就出在幂等性上。"超时了重试一次"这个看似无害的动作,在非幂等接口上就是灾难。
4. 状态码语义与线上排查:一眼定位问题
4.1 状态码分类速查
状态码是服务器给客户端的最直接反馈。很多开发者只知道 200 表示成功、404 表示找不到,但线上问题的排查往往就藏在那些不常见的码里。
状态码按百位数字分类:
1xx 是信息性响应,平时基本见不到,最典型的是 101 Switching Protocols,用于 WebSocket 升级协议。
2xx 是成功,200 最常规,201 表示资源创建成功,204 表示响应没有消息体。
3xx 是重定向,301 永久跳转、302 临时跳转、304 表示命中协商缓存未修改。这里容易混淆的是 301 和 302,尤其是做 URL 迁移时,该用 301 还是 302 要想清楚,用错了会影响搜索引擎收录。
4xx 是客户端错误。400 通用参数错误、401 未认证、403 权限不足、404 资源不存在、405 方法不允许、409 冲突。最容易让人头疼的是 401 和 403 的区别:401 是说你"没有证明你是谁",403 是"已经证明你是谁了,但你没有权限"。如果分不清这两个,排查权限问题时会走弯路。
5xx 是服务端错误。500 通用错误、502 网关收到上游无效响应、503 服务不可用、504 网关超时。5xx 类的错误往往和网关配置、上游服务状态、超时设置有关。
4.2 高频状态码的排查案例
先拿 502 来说。502 Bad Gateway 意味着网关(比如 Nginx)已经收到了你的请求,但上游应用没有给出有效响应。常见原因有三个:上游服务挂了、上游服务超时了、上游服务的响应被网关判定为无效。排查时第一步是看上游应用日志,确认它到底有没有收到这个请求。如果收到了但处理慢,就需要调大网关的 proxy_read_timeout;如果压根没收到,就要检查 IP 和端口的连通性。
再来看 504 Gateway Timeout。这个状态码是网关等不及上游的回应,主动放弃了。排查看两个时间:一是网关的超时配置,二是上游应用的实际处理时间。如果接口确实需要长耗时,比如导出报表要跑 30 秒,那要么调大超时时间,要么改成异步任务加轮询,不要硬扛同步等待。
429 Too Many Requests 是限流状态码。服务端通过这个码告诉你"请求太频繁了,歇会儿再来"。配合 Retry-After 头使用,客户端就能知道到底要等多久。线上系统如果这个码出现得频繁,要么是客户端被恶意刷量,要么是限流参数的阈值设得太低。正常业务被误伤时,响应里带一个 X-RateLimit-Remaining 之类的头会方便排查。
304 Not Modified 经常被误解为"报错",其实它是在说:你之前缓存过这个资源,经过协商,服务器确认它没有变化,你就继续用本地缓存吧。这个状态码对页面性能的影响非常积极,它让浏览器避免了重复下载大文件。
5. 连接管理与性能优化:从 keep-alive 到多路复用
5.1 TCP 连接复用与浏览器并发限制
HTTP 底层靠 TCP 传输。TCP 连接建立一个来回成本不低,三次握手外加上 TLS 的话还要再加几轮。所以 HTTP/1.1 引入的 keep-alive 机制对性能的提升非常可观:一个 TCP 连接建立后,可以反复发送多个请求。
但 keep-alive 不是万能的。HTTP/1.1 的持久连接上,请求响应是严格一对一的顺序关系。一个请求没结束,后面的请求就得排队。这就是 HTTP 队头阻塞。浏览器为此只能多方下注,一个域名同时建立多个 TCP 连接,但连接总数又不能无限膨胀,于是就有了并发连接数的限制,默认一般是 6 个。
你打开一个网页,浏览器会为这个域名自动建立最多 6 个 TCP 连接来并行加载资源。如果你用浏览器开发者工具网络面板能看到这些连接,会发现每个标签页里的请求被分到了不同的 Connection ID 上。这个机制决定了:HTTP/1.1 下的页面性能优化,一个重要思路就是把资源分散到多个域名,绕过连接数限制。这也是早年"域名分片"技术的由来。
5.2 队头阻塞:HTTP/1.1 的软肋与 HTTP/2 的解法
HTTP/1.1 的队头阻塞在于"应用层必须按顺序处理"。虽然你可以开多个连接,但单个连接上还是得排队。HTTP/2 的多路复用在这个层面解决了问题:一个 TCP 连接里面开了多个流,流的帧可以交错传输,互不影响。
这就好比 HTTP/1.1 是一条单车道,只能一辆车一辆车过;HTTP/2 是在同一条路上画出了多车道,每个请求的车可以并行开。代价是实现的复杂度明显上升,帧、流、优先级这些概念都是 HTTP/1.1 时代没有的。
但 HTTP/2 并没有治愈所有队头阻塞。它只是把 HTTP 层的队头阻塞消除了,TCP 层依然有一个问题:TCP 段丢失会导致后续数据等待重传。因为 HTTP/2 的所有流都共享一个 TCP 连接,一个 TCP 包丢了,会影响所有流。这也解释了为什么弱网条件下 HTTP/2 有时候反而比 HTTP/1.1 慢,以及 HTTP/3 为什么会改走基于 UDP 的 QUIC。
做性能优化时我的建议是:如果你的站点还在用 HTTP/1.1,而且静态资源很多,优先升级到 HTTP/2。大多数时候这是成本最低、见效最明显的一次协议层优化。如果升级以后发现弱网下表现依然一般,再考虑 HTTP/3。
6. 缓存机制:让浏览器帮你少跑几趟
6.1 强缓存与协商缓存的分工
HTTP 缓存是性能优化里面最值得花时间的一块。理解缓存机制之后,很多"页面加载慢"的问题可以直接在无代码改动的前提下解决掉。
缓存分两大类:强缓存和协商缓存。强缓存的意思是浏览器直接从本地缓存里取资源,不发任何请求给服务器。它靠两个头控制:Expires 和 Cache-Control。其中 Cache-Control 是更现代的方案,优先级也更高。max-age=3600 表示这个资源在 3600 秒内都可以直接用本地缓存。
协商缓存的意思是浏览器拿本地缓存的资源去问服务器"这个还新鲜吗?"服务器根据条件判断,如果资源没变,就返回 304,浏览器继续用本地缓存;如果变了,就返回 200 加新的资源内容。协商缓存的判断依据主要是 ETag 和 Last-Modified。ETag 是资源内容的一个哈希标识,Last-Modified 是资源最后修改的时间。
注意一个优先级问题:Cache-Control 的优先级高于 Expires,ETag 的优先级高于 Last-Modified。也就是说,如果同时定义了强缓存时间未过期,根本不会走到协商缓存那一步。只有强缓存失效了,才会带着 ETag 或 Last-Modified 去服务器做新一轮协商。
6.2 缓存策略设计实战参考
设计缓存策略的时候,核心问题是想清楚"哪些资源可以放心缓存,缓存多久"。
日常项目里最常用的一个参考策略是这样的:
- 静态资源文件,比如图片、CSS、JS 文件,如果文件名带版本哈希(比如 app.8f3k2a.js),可以用 Cache-Control: max-age=31536000,也就是一年。因为文件名变了就是新资源,旧缓存永远不会被错误使用。
- HTML 页面不做强缓存,或者只做很短时间的强缓存,比如 max-age=60,然后配合协商缓存来控制刷新速度。这样发布新版本后,用户最多晚 60 秒看到首页更新。
- API 接口一般不主动设置缓存头。需要缓存的话,多用协商缓存,避免返回过期的业务数据。
实际踩坑最多的场景是这样的:静态资源没有带版本号,却又设置了很长的缓存时间。这会导致文件更新后,用户浏览器还在用旧文件,怎么刷新都没用。解决方法是强制刷新(Ctrl+F5),或者给资源加个新的查询参数。但这个方法治标不治本,最干净的方案还是让文件名随内容变化。
另外提一下 Service Worker 层面的缓存。它属于前端的离线缓存机制,可以在网络不可用的情况下直接返回缓存。它的优先级比 HTTP 缓存更高,因为 Service Worker 在网络请求发出之前就已经拦截了。这种缓存适合离线优先的应用,但要注意版本管理和快速更新策略,否则会出现"用户永远在用老页面"的问题。
7. HTTPS:HTTP 的安全外衣
7.1 TLS 握手到底在干什么
HTTPS 本质上就是 HTTP over TLS,它做的事情是在 TCP 之上加了一层安全协议。TLS 要解决两个问题:一是确保通信对象是真的,不是冒牌货;二是确保传输内容加密,路上被人截获也看不懂。
TLS 握手的第一步是客户端和服务器协商加密套件。客户端发一个 ClientHello,列出自己支持的 TLS 版本和加密算法;服务器从中选一个,回 ServerHello。接着服务器把自己的证书发给客户端。这一步非常关键,证书里面包含了服务器的公钥,以及可信机构对证书内容的签名。
客户端拿到证书后,会用本地保存的根证书去验证签名是否可信。验证通过,客户端生成一个对称加密密钥,用服务器的公钥加密后发给服务器。服务器用私钥解密,双方就获得了同一个会话密钥。之后的数据传输全部用这个会话密钥做对称加密。
握手一共需要几次往返?老版本 TLS 1.2 需要 2 个 RTT,TLS 1.3 优化到了 1 个 RTT。HTTP/3 更进一步,直接把 TLS 集成到协议里,建连和握手同步完成,所以可以做到 0-RTT 或 1-RTT。
7.2 证书校验里的几个坑
证书是 HTTPS 里面最容易出问题的地方。最常见的坑:自签名证书导致客户端不信任。开发环境里大家都图省事自己签发证书,但到了真机上或者浏览器里,CA 校验不过,连接直接失败。解决方法是把自签名证书导入系统的信任列表,或者直接给开发环境安装一个本地 CA 工具来签发证书。
另一个坑是证书过期。很多线上故障都源于运维忘了定期更新证书,用户打开页面直接显示"连接不安全"。排查这种问题不需要看应用日志,直接在服务器上用命令检查证书有效期就行。我习惯把证书续期做成自动化任务,配上到期前 30 天的告警,杜绝人工遗忘。
还有就是证书链不完整的问题。服务器返回的证书链如果缺少中间证书,客户端无法完成链式验证。很多用户安装证书时只装了服务器证书,没把中间证书一并配置,导致一部分客户端能访问、一部分提示不安全。验证证书链可以用在线检测工具,也可以本地模拟握手查看返回的完整证书链。
还有一个日常容易忽视的点:混合内容。页面本身是 HTTPS 的,但里面嵌了 HTTP 的图片或脚本,浏览器就会阻止这些不安全的内容加载。排查这类问题,打开开发者工具的 Console 就能看到明确的警告信息,把对应资源改成 HTTPS 地址就好。
8. 常见问题排查与工具用法:实测几个典型案例
8.1 curl 里的高频调试参数
排查 HTTP 问题,curl 是我第一个打开的工具。效率极高,而且能精确控制请求内容。
最基础的是 -v 参数,它能打印出整个请求和响应过程的细节,包括 TLS 握手信息、请求头、响应头、耗时等。调试接口连通性、证书问题时,-v 是不可或缺的。
-H 用于添加请求头,比如curl -H "Authorization: Bearer xxx"。-d 用于发送请求体,默认会带上 POST。如果想看响应头,单独加 -i。想模拟请求来自某个域名,用 -H "Host: example.com"。想测试走了 HTTP/2,用 --http2。
curl 还有一个隐藏能力是可以分段查看耗时。用curl -w输出时间变量,可以分别看到域名解析时间、TCP 连接时间、TLS 握手时间、首字节时间、总时间。这个对定位"慢在哪个环节"很有帮助。比如 DNS 解析时间异常长,那就去查本地解析配置;TLS 握手时间过长,就重点看证书链和加密套件协商。
8.2 三个真实场景排查复盘
说几个我实际遇到过的案例。
第一个案例:接口偶发超时,HTTP 状态码是 200,但响应体为空。查了应用日志,发现服务端确实收到了请求,处理逻辑也跑了,但 Nginx 层返回了空内容。最终定位到是网关和上游之间的 keep-alive 连接被服务端回收,网关不知道,还在用旧连接发请求,导致请求没有到达应用就被丢弃。解决办法是在 Nginx 和服务端之间统一连接空闲超时配置,并且适当设置 proxy_http_version 1.1 和 Connection 头。
第二个案例:页面图片偶尔加载不出来。用浏览器开发者工具看,发现加载失败的图片都带 403 状态码。检查后发现是图片所在域名的防盗链配置生效了,浏览器发出的 Referer 被服务器拒绝。解决方式是把当前业务域名加进防盗链白名单。这个案例说明,403 不一定是权限问题,也可能是 Referer 校验问题。
第三个案例:客户端反反复复报"请求头太大"。查看 Nginx 日志发现确实有 400 错误。原因很简单,某个页面塞了太多 Cookie,超过了 Nginx 默认的 large_client_header_buffers 限制。解决办法是调整 Nginx 的这个参数,但更根本的方向是减少 Cookie 的传输。把不必要的 Cookie 改成短域名或者直接清理,问题就不再复发。
8.3 浏览器开发者工具怎么看协议
浏览器开发者工具除了看 Response 和 Timeline 之外,还有一个很容易被忽略的表格列:Protocol。在 Network 面板的表头右键勾选 Protocol,就能看到每个请求走的是 h1、h2 还是 h3。
这个信息对排查性能问题非常有用。如果你发现某个域名下所有资源走的是 h1,说明 HTTP/2 没有生效;如果看到有些请求显示为 h3,说明已经在走 QUIC 了。配合 Connection ID 列,还能看到 HTTP/1.1 下同一个域名建了多少条连接,多路复用是否真的在用。
开发者工具里另外一个值得多用的是"Copy as cURL"功能。右键任何一条网络请求,可以直接复制成一条 curl 命令。这在复现线上问题的时候特别好用:从 DevTools 复制命令到终端,加一个 -v 就能完全复现请求过程的细节,排查效率和纯猜完全不一样。
9. 从 HTTP/1.1 升级到 HTTP/2:迁移配置要点
9.1 开启 HTTP/2 的前置条件
如果没有反代层或者网关层,你通常只需要在入口处开启 HTTP/2。以常见的反向代理服务器配置为例,开启 HTTP/2 的代码就是listen 443 ssl http2;(不同版本写法略有差异),但真正决定"能不能升"的前提是:HTTPS 必须已经配置好。HTTP/2 在主流浏览器里都要求走 TLS,所以你首先得把 HTTPS 彻底搞定。
第二个前置条件:证书链要完整。HTTP/2 对证书的要求比 HTTP/1.1 更严格,某些实现下证书链不完整会直接导致握手失败。升级之前先把证书链检测一遍,把中间证书补全。
第三是排查是否有依赖 HTTP/1.1 特性的旧逻辑。比如有些客户端可能手动组装 HTTP 报文,如果它不识别 HTTP/2 的帧格式,就会出问题。不过正常的浏览器和现代网络库都不存在这个问题,主要是排查老旧的 SDK。
9.2 打开 HTTP/2 之后的变化
开启 HTTP/2 后,最直观的感受是:大量的静态资源请求不再排队,多个资源可以在同一连接上并行传输。拿一个有很多小图标的页面来说,HTTP/1.1 下浏览器要开 6 条连接甚至等待排队,HTTP/2 下所有请求在一条连接上同时跑,加载速度提升非常明显。
开启 HTTP/2 之后还需要关注优先级策略。HTTP/2 支持给流设置优先级,比如页面主 HTML、首屏 CSS 和 JS 可以设置高优先级,懒加载图片设置低优先级。合理设置优先级可以大幅改善首屏渲染速度。但优先级设错了也可能导致资源互相等待,反而拖慢速度。新手建议先别调优先级,让默认行为跑一段时间,通过核心指标来观察实际收益。
从 HTTP/1.1 切到 HTTP/2 之后,之前为绕过连接数限制做的"域名分片"反而成了负担。因为 HTTP/2 本来就在一条连接里多路复用,多个域名反而降低了连接复用率,还增加 DNS 解析成本。所以迁移后一个重要的清理工作就是:把分片域名收敛回来,能合并成一个域名的就合并成一个。
最后提一句 HTTP/3 的开启。目前主流服务器软件都有对应的 QUIC 配置,但 HTTP/3 依赖 UDP 端口,要注意防火墙和负载均衡是否放行相关流量。客户端浏览器的兼容性也还在演进,不建议在没有充分测试的情况下直接全量切换。可以灰度一部分流量,观察传输失败率、加载时间等指标,稳了再铺开。
我个人在实际操作中最大的体会是:HTTP 协议不像高深的算法那样需要天赋,它更像一套约定俗成的交通规则。规则本身不复杂,但用得好不好、排查问题时有没有意识到"这里是靠规则决定行为的",差距就非常大了。放下抓包工具前,多问自己一句"这个状态码、这个头、这个超时参数,背后的语义是什么",很多疑难杂症自己就露出了答案。把这篇总结里的内容全部消化掉,再回头看日常开发里的网络问题,你会觉得清晰得可怕。