Skip to main content
线上排查加载慢时,我一般先从缓存头和连接复用看起,HTTP 的问题大多落在这两处。

Q1: HTTP/1.1 vs HTTP/2 vs HTTP/3 有什么区别?

1

HTTP/0.9 与 HTTP/1.0: 文本协议的诞生

1991 年 HTTP/0.9 只支持 GET 请求和纯文本响应。1996 年 HTTP/1.0 引入了状态码、头部字段和 Content-Type,但每个请求都需要建立新的 TCP 连接。
2

HTTP/1.1 的致命痛点: 队头阻塞

HTTP/1.1 默认开启并规范化了持久连接(Keep-Alive)——该机制在 HTTP/1.0 已有扩展实现,但同一连接中请求必须串行——前一个请求的响应没回来,后一个请求只能等着。浏览器对同一域名限制 6-8 个并发连接,多资源页面加载效率低下。HTTP/1.1 还提出过管线化(pipelining),但响应仍须按请求顺序返回,且主流浏览器默认关闭,实际并未解决队头阻塞。
3

HTTP/2 的革命: 多路复用

HTTP/2 引入了二进制分帧和多路复用——单个 TCP 连接上可以并行传输多个请求/响应。数据被拆分为帧(Frame),不同流的帧交错传输。还引入了 HPACK 头部压缩和服务器推送(Server Push 后来因收益有限被主流浏览器弃用)。
4

HTTP/2 的遗留问题: TCP 层队头阻塞

虽然 HTTP 层的多路复用解决了 HTTP 队头阻塞,但 TCP 是字节流协议——一个 TCP 包丢失,所有流都得等重传。在高丢包率网络下,HTTP/2 的性能可能反而不如 HTTP/1.1 的多连接策略。
5

HTTP/3 的终极方案: QUIC

HTTP/3 用 QUIC(基于 UDP)替代 TCP。QUIC 的流是独立的——Stream 2 的包丢失只阻塞 Stream 2,Stream 1 和 3 正常交付。QUIC 还内置了 TLS 1.3(1-RTT 握手),支持 0-RTT 恢复和连接迁移。
HTTP 版本演进解决的核心问题:
HTTP 协议的演进核心是解决队头阻塞——从 HTTP/1.1 的串行、到 HTTP/2 的多路复用、再到 HTTP/3 的独立流。

HTTP/1.1 的队头阻塞

就像一条单车道公路,前面的车抛锚了,后面的车全部堵死。HTTP/1.1 中一个慢请求会阻塞同连接上的所有后续请求,浏览器只能开 6 条车道(并发连接)来缓解。

HTTP/3 的独立流

HTTP/3 就像一条多车道高速公路,每条车道完全独立。一辆车抛锚只堵自己那条道,其他车道畅通无阻。QUIC 基于 UDP 实现了流级别的独立性,彻底解决了队头阻塞。
回答 HTTP 版本演进时,核心叙事是”解决队头阻塞”:HTTP/1.1 串行 → HTTP/2 多路复用但有 TCP 队头阻塞 → HTTP/3 用 QUIC 的独立流彻底解决。不要只罗列特性,要说清楚”为什么要改”。

Q2: HTTPS 的握手过程是什么?

HTTPS = HTTP + TLS,提供三个保障:机密性(数据加密)、完整性(不可篡改)、身份认证(确认服务器身份)。核心流程如下:
浏览器验证服务器身份依赖证书信任链,不是直接信任服务器证书:
  • 根证书(预装在操作系统/浏览器中)→ 签发中间证书
  • 中间证书 → 签发服务器证书
  • 浏览器从服务器证书向上追溯到根证书,逐级验证签名
如果链条中任何一环过期、被吊销或签名不匹配,浏览器会显示安全警告。Certificate Transparency(CT)日志让所有签发的证书都可公开审计,防止 CA 被攻破后签发伪造证书。
TLS 握手就像两个特工在公共场所接头。ClientHello 是暗号(“我支持哪些加密方式”),ServerHello 是回应(“我选这个方式”)。Certificate 是工作证(“这是我的身份证明,你可以查一下”)。ClientKeyExchange 是交换密码本(“我用你的公钥加密了密码本的种子”)。双方各自用种子生成相同的密码本(会话密钥),之后所有通信都用密码本加密。TLS 1.3 的改进就像特工提前把密码本的种子附在暗号里发出(Key Share),省掉了一次来回确认。0-RTT 更激进——如果之前接过头,下次直接用旧密码本发加密消息,连暗号都省了(但有被录音重放的风险)。

证书信任链与 Certificate Transparency

浏览器验证服务器身份依赖证书信任链:根证书(预装在操作系统/浏览器中)签发中间证书,中间证书签发服务器证书。浏览器从服务器证书向上追溯到根证书,逐级验证签名。Certificate Transparency(CT)日志让所有签发的证书都可公开审计,防止 CA 被攻破后签发伪造证书。

TLS 1.3 的安全改进

TLS 1.3 移除了所有不安全的加密算法(如 RSA 密钥交换、CBC 模式),只保留 AEAD 加密(如 AES-GCM)。这意味着即使攻击者截获了握手过程中的所有消息,也无法推导出会话密钥——因为密钥交换使用的是 ECDHE(椭圆曲线 Diffie-Hellman 临时密钥),每次连接生成全新的密钥对。

Q3: HTTP 缓存策略是什么?

基础梳理

专家视角深度剖析

很多人能背出”强缓存和协商缓存”,但实际更在意的是:在实际工程中,不同资源应该用什么缓存策略?为什么? 缓存策略的核心原则是:不变的资源用强缓存,会变的资源用协商缓存。 具体来说: HTML 文件是入口文件,内容会随版本更新,应该用 Cache-Control: no-cache(每次都协商缓存),配合 ETag 让服务器判断是否有变化。JS/CSS/图片等静态资源应该用 Cache-Control: max-age=31536000(强缓存 1 年),配合构建工具生成的 hash 文件名(如 app.a1b2c3.js)——内容变了文件名就变了,自动失效。API 响应通常用 Cache-Control: no-store(完全不缓存)。

Q4: 跨域有哪些解决方案?

CORS 的原理: 浏览器的同源策略限制了跨域请求。CORS(Cross-Origin Resource Sharing)通过在响应头中声明允许的来源,让浏览器放行跨域请求。非简单请求(使用 PUT/DELETE 等方法、自定义请求头,或 Content-Type 不是三个安全值之一)会触发 preflight——浏览器先发 OPTIONS 请求询问服务器是否允许。 同源策略的限制:
生产环境用 CORS(服务端配置 Access-Control-Allow-Origin),开发环境用 Vite/Webpack 的 proxy 代理。JSONP 已经过时,不要在新项目中使用。CORS preflight 请求(OPTIONS)会增加一次网络往返,对于频繁的跨域 API 调用,可以考虑在 Nginx 层做反向代理消除跨域。

底层源码:Chromium 是如何实现的?

Chromium 的网络栈分层实现在 net/ 目录中(详见 Chromium 源码:net/http/http_network_transaction.cc):
网络栈采用分层设计:最底层是 socket(TCP/UDP),之上是 TLS 加密层,再之上是 HTTP 协议层。HTTP/2 通过 SpdySession 管理单个 TCP 连接上的多个流,HTTP/3 通过 QuicSession 管理 QUIC 连接。 详见 Chromium 源码:net/quic/quic_session.cc:
QUIC 的连接迁移依赖 Connection ID 机制:每个连接有一个不依赖网络地址的唯一标识,当客户端 IP 变化时(如 Wi-Fi 切换到 4G),只需将新地址和 Connection ID 关联即可恢复连接。这解决了 TCP 连接在 IP 变化时必须重新握手的问题。

及格回答 vs 满分回答

问题:“说说 HTTP/2 和 HTTP/3 的区别”

及格回答(60-70分):
“HTTP/2 有多路复用、头部压缩、二进制分帧。HTTP/3 用 QUIC 替代 TCP,解决了 TCP 层的队头阻塞。”
问题出在:知道特性但不理解设计权衡。 满分回答(90-100分):
HTTP/2 的核心改进是多路复用——单个 TCP 连接上并行传输多个请求/响应,用流 ID 标识每个请求。配合 HPACK 头部压缩(静态表 + 动态表 + Huffman 编码),重复头部只传索引号。但 TCP 是字节流协议,一个 TCP 包丢失会阻塞所有流——这是 TCP 层的队头阻塞。《High Performance Browser Networking》系统讨论了 HTTP/2 多路复用,并指出 TCP 层的字节流有序交付机制仍是性能瓶颈。[1]
HTTP/3 用 QUIC(基于 UDP)彻底解决这个问题。QUIC 的流是独立的——Stream 2 的包丢失只阻塞 Stream 2,Stream 1 和 3 正常交付。QUIC 还内置了 TLS 1.3(1-RTT 握手),支持 0-RTT 恢复和连接迁移。连接迁移通过 Connection ID 实现——不依赖 IP:Port,Wi-Fi 切换到 4G 时连接不断开。Cloudflare 在《HTTP/3: the past, the present, and the future》中指出,QUIC 由 Google 开发后被 IETF 标准化为开放协议,所有 HTTP/3 连接默认加密(内置 TLS 1.3),这是 HTTP 协议历史上首次在传输层强制加密。[2]
在 Chromium 中,HTTP/2 实现在 net/spdy/ 目录(SPDY 是 HTTP/2 的前身),HTTP/3 实现在 net/quic/ 目录(基于 Google 的 quiche 库)。两者的上层接口统一在 net/http/ 中,对应用层透明。理解 HTTP 协议栈的分层设计(socket → TLS → HTTP),是从”会发请求”到”知道浏览器和服务器之间到底发生了什么”的关键跃迁。
HTTP/2 不一定比 HTTP/1.1 快。在单个请求的简单场景(二进制解析开销)、高丢包率网络(TCP 队头阻塞更严重)、服务器配置不当等情况下,HTTP/2 可能更慢。选择协议要结合实际场景。

常见追问

题目来源于真实面经(字节/腾讯/阿里前端面试、牛客网、haizlin/fe-interview),按难度递进。
答题要点: 不能只答”强缓存优先”,要说清楚 Cache-Control 和 Expires 的优先级差异。高分回答: “缓存判断的优先级是:强缓存 > 协商缓存。强缓存中,Cache-Control 的优先级高于 Expires——因为 Expires 是绝对时间,受客户端时钟影响(用户改系统时间就会出问题),而 Cache-Control 是相对时间(max-age=3600 表示从响应开始算 3600 秒)。协商缓存中,ETag 的优先级高于 Last-Modified。Last-Modified 精度只到秒——如果文件在 1 秒内修改了多次,或者文件内容没变但修改时间变了(如重新部署),Last-Modified 会误判。ETag 是内容哈希,内容不变就不失效。但 ETag 计算有性能开销(需要读取文件内容生成哈希),所以通常两者配合使用。”
答题要点: 结合”简单请求”的定义和 preflight 的保护目的解释。高分回答: “CORS 将请求分为简单请求和非简单请求。简单请求满足三个条件:1)方法是 GET/HEAD/POST;2)头部只有安全字段(Accept、Content-Type 等);3)Content-Type 只限 text/plain、multipart/form-data、application/x-www-form-urlencoded。简单请求直接发送,浏览器检查响应头的 Access-Control-Allow-Origin。非简单请求(如 PUT/DELETE、自定义 Header、application/json 的 Content-Type)会触发 preflight——浏览器先发一个 OPTIONS 请求,询问服务器是否允许该跨域请求。服务器响应允许后,浏览器才发真正的请求。Preflight 的设计初衷是保护旧服务器——旧服务器不理解 CORS,但也不会执行副作用操作(OPTIONS 是安全的)。减少 preflight 的方式:1)用简单请求的 Content-Type;2)服务器配置 Access-Control-Max-Age 缓存 preflight 结果;3)对于频繁的跨域 API 调用,用 Nginx 反向代理消除跨域。”
答题要点: 必须分场景讨论——HTTPS 的保护范围和边界。高分回答: “HTTPS 能有效防止中间人攻击,但有几个边界场景需要注意:1)证书伪造:如果攻击者在用户设备上安装了自签名根证书(如企业代理),就能解密所有 HTTPS 流量。这就是为什么企业抓包工具(如 Charles)需要先安装根证书。 2)降级攻击:攻击者拦截 TLS 握手,强制降级到不安全的协议版本。防御方式是服务器配置 HSTS(HTTP Strict Transport Security),强制浏览器只用 HTTPS。 3)证书吊销延迟:CA 吊销证书后,浏览器需要通过 OCSP 或 CRL 查询证书状态。如果浏览器没有及时更新(软失败策略),可能仍然信任已吊销的证书。TLS 1.3 移除了所有不安全的加密算法(如 RSA 密钥交换、CBC 模式),只保留 AEAD 加密(如 AES-GCM),大幅提升了安全性。”
答题要点: 对比常见认知,点出 Server Push 的实际问题。高分回答: “HTTP/2 的 Server Push 理论上很美好——服务器预测客户端需要的资源,在客户端请求前就推送过去,省掉一次往返。但实际使用中发现了严重问题:1)缓存浪费:服务器不知道客户端是否已经缓存了该资源。如果客户端已有缓存,推送就是浪费带宽。HTTP/2 没有好的机制让客户端告知服务器’我已经有了’。 2)优先级冲突:推送的资源和客户端主动请求的资源竞争带宽。如果推送了不重要的资源,可能阻塞了客户端真正需要的关键资源。 3)实现复杂:服务器需要精确预测客户端需要什么资源,这在动态页面中几乎不可能。Chrome 在 v106 中默认禁用了 HTTP/2 Server Push。替代方案是 103 Early Hints——服务器在最终响应前先返回一个 103 状态码,告诉客户端’你可能需要这些资源,先预加载’。客户端可以根据自己的缓存状态决定是否请求,避免了 Server Push 的缓存浪费问题。“

延伸阅读与引用参考