一道面试题,开始性能优化之旅(4)-- Request && Response

81 阅读6分钟

终于跨过 TCP,我们来到 发送http请求,响应阶段

image.png

TTFB

所谓 TTFB 就是 responseStart- requestStart

一、TTFB的“真面目”与技术本质

graph TD
    A[浏览器发起请求] --> B[服务器接收]
    B --> C[业务逻辑处理]
    C --> D[响应生成]
    D --> E[网络传输] 
    E --> F[浏览器接收首字节]  --> G[TTFB结束点]
  • TTFB真实包含网络延迟与真实业务处理耗时
    • 网络往返延迟(RTT)
    • 服务器队列等待时间
    • 后端处理时间(Server RT)
    • 首字节网络传输时间(通常极小)

如何优化TTFB

既然 TTFB真实包含:网络延迟与真实业务处理耗时,那么业务耗时主要在服务器中优化,对于前端可以做的就是 网络延迟优化

如何优化网络往返延迟(RTT)-- 尽量减少请求头和请求体

示例:一个登录请求,通常需要发送用户名和密码,但有时前端可能会附带很多额外的元数据(如设备信息、跟踪信息等)。如果这些信息不是登录所必需的,应该移除或通过其他方式发送(比如在登录成功后发送)。

技术细节:

  • 在HTTP/1.1中,每个请求都需要完整发送头信息,且头信息不压缩(HPACK仅在HTTP/2中引入)。
  • 即使使用HTTP/2,头信息使用HPACK压缩,但过大的头仍然会带来传输负担。
  • 对于请求体,使用GZIP等压缩方式可以减小体积,但压缩和解压缩都需要时间,而且对于已经压缩的数据(如图片)效果不大。

优化策略:

  1. 减少Cookie大小:

    • 避免在Cookie中存储不必要的数据。
    • 使用服务器端会话存储(Session Storage),只在Cookie中存储一个会话ID。
    • 设置合理的Cookie过期时间。
  2. 减少POST请求体大小:

    • 移除不必要的字段。
    • 对数据进行压缩(如JSON数据使用GZIP压缩,但注意:客户端需要设置Content-Encoding,并且服务器需要支持解压缩)。
    • 对于大文件上传,使用分片上传(multipart upload)或断点续传,但这通常是为了避免单次请求超时,而不是为了减小单次请求大小。
  3. 使用HTTP/2:

    • HTTP/2的多路复用和头部压缩可以减轻头部负担,但请求体过大的问题仍然存在。
  4. 考虑使用查询参数代替请求体(仅适用于GET):

    • 对于GET请求,数据通过URL参数传递。注意URL长度限制(不同浏览器限制不同,一般为2KB-8KB),而且URL参数在日志中容易被记录,不适合敏感数据。

实际测量: 使用浏览器开发者工具或Wireshark,可以观察到:

  • 当请求头或体很大时,在发送请求阶段(Request sent阶段)会占用较长时间。
  • 在WebPageTest的瀑布图中,你会看到发送请求(Request)的横条很长。

结论: 为了优化性能,特别是减少TTFB,应该尽量减小请求的大小,特别是避免在请求头中携带过大的Cookie,以及在POST请求中发送过大的请求体。

如何优化Response 阶段性能

传输速度和压缩速度如何兼得

一、内容压缩原理:客户端与服务端的协商机制

1. 关键 Header 的作用

Header位置功能
Accept-Encoding请求头 (Request)客户端声明支持的压缩算法(如 gzip, deflate, br),服务端从中选择。
Content-Encoding响应头 (Response)服务端声明实际使用的压缩算法(如 gzip),客户端据此解压。

2. 工作流程(图 11-2)

  1. 客户端请求:携带 Accept-Encoding: gzip, br
  2. 服务端响应:
    • 选择一种客户端支持的算法(如 gzip)压缩内容。
    • 返回响应头 Content-Encoding: gzip + 压缩后的数据。
  3. 客户端解压:根据 Content-Encoding 用对应算法解压数据。

价值:减少网络传输量(通常压缩后体积减少 60%~80%),显著提升加载速度。


二、主流压缩算法对比

算法压缩率压缩速度解压速度适用场景
Gzip中等⚡️ 快⚡️ 快通用文本(HTML/CSS/JS)
Brotli⚠️ 慢⚡️ 快静态资源(高压缩率优先场景)
Deflate⚡️ 快⚡️ 快历史遗留系统(已逐渐淘汰)
  • 关键结论
    • Brotli(br) 压缩率更高(比 Gzip 高 15%~20%),但压缩耗时显著更长(图 11-4)。
    • Gzip 在压缩速度与压缩率间取得平衡,适合实时压缩。

三、压缩策略:实时压缩 vs 预压缩

1. 实时压缩(图 11-5)

  • 工作原理
    服务端(如 Nginx)在响应请求时动态压缩内容(如配置 gzip on;)。
  • 优点
    • 灵活处理动态内容(如 API 响应)。
    • 节省存储空间(无需存储压缩副本)。
  • 缺点
    • 增加 CPU 开销:每次请求需实时压缩。
    • 增加响应延迟:压缩耗时计入 TTFB(首字节时间)。

2. 预压缩

  • 工作原理
    提前将静态文件(如 app.js)压缩为 app.js.gz/app.js.br,服务端直接发送预压缩文件。
  • 优点
    • 零压缩延迟:直接读取已压缩文件,TTFB 更低。
    • 降低 CPU 负载:避免重复压缩。
  • 缺点
    • 需额外存储空间(保留原始文件与压缩文件)。
    • 仅适用于静态资源。

3. 性能权衡

“传输时间节约 > 压缩耗时 + 解压耗时”

  • 典型场景
    • 1 MB 文本文件 → Gzip 压缩后 200 KB。
    • 压缩耗时 50ms + 解压耗时 20ms = 总成本 70ms
    • 传输节约:800 KB(假设 10 Mbps 带宽 → 节省 640ms)。
    • 净收益:570ms(网络节省远大于压缩开销)。
  • 例外
    高速网络(如 5G)下小文件( 10 KB)可能得不偿失。

四、静态资源压缩的进阶策略:从实时压缩到离线压缩

1. 实时压缩的瓶颈

  • 问题
    大文件(如 1MB+ 的 JS/CSS)使用 Brotli(br)实时压缩时:
    • 压缩耗时高(图 11-4 中 br 压缩时间 ≈ Gzip 的 3-5 倍)。
    • 总耗时可能增加:压缩时间 > 传输节省时间(尤其在低延迟网络中)。
    • 未压缩文件:传输 2 MB(耗时 1600ms @ 10 Mbps)。
    • br 实时压缩:压缩耗时 300ms → 压缩后 400 KB → 传输 320ms。
    • 总耗时:300ms(压缩)+ 320ms(传输) = 620ms(反而高于未压缩方案)。

2. 离线压缩(预压缩)的解决方案

  • 原理
    • 在代码构建阶段(如 Webpack)提前生成压缩文件(如 app.js.br, style.css.gz)。
    • 服务端直接托管这些文件,省去实时压缩开销
  • 工作流程
    1. 客户端请求:Accept-Encoding: gzip, br
    2. 服务端检查:
      • 若存在 app.js.br → 返回 Content-Encoding: br + 预压缩文件。
      • 若存在 app.js.gz → 返回 Content-Encoding: gzip + 预压缩文件。
    3. 零压缩延迟:TTFB(首字节时间)显著降低。

优势

  • 解决 Brotli 压缩慢的问题,发挥其高压缩率优势(比 Gzip 小 15%~25%)。
  • 降低服务端 CPU 压力,提升并发能力。

五、压缩的禁区:媒体文件

“图片/视频无需额外压缩”

  • 原因
    • 图片(JPEG/PNG/WebP)、视频(MP4/WebM)本身是有损压缩格式
    • 二次压缩(如 Gzip)几乎不减小体积,反而浪费 CPU。
  • 检测方法
    • 在 DevTools 的 Network 面板查看资源大小:
      • Size(传输大小) ≈ Content-Length(原始大小)→ 压缩无效。