跳到主要内容
预计阅读 22 分钟

Web 性能优化 — 让你的网站快如闪电

用户的耐心极其有限——页面加载超过 3 秒,超过一半的用户会直接离开。Web 性能优化不仅是技术问题,更是直接影响用户体验和商业收入的关键因素。本章将从网络层面系统讲解提升 Web 性能的核心技术。

📋 开篇自测:你已经知道多少?

  1. CDN 是如何加速内容分发的?它的回源机制是怎样的?
  2. 四层负载均衡和七层负载均衡的区别是什么?各有什么优缺点?
  3. WebSocket 和 HTTP 长轮询有什么区别?WebSocket 的握手过程是怎样的?

一、CDN:把内容搬到用户身边

1.1 CDN 的工作原理

CDN(Content Delivery Network,内容分发网络)通过在全球各地部署边缘节点,将内容缓存到离用户最近的服务器上:

没有 CDN:
北京用户 -----(跨越2000公里)-----> 深圳源站
延迟: 50-100ms

使用 CDN:
北京用户 -----(几十公里)-----> 北京CDN边缘节点
延迟: 5-10ms

CDN 架构:
                        +-------------+
                        |   源站服务器  |
                        +------+------+
                               |
                    +----------+----------+
                    |     CDN 中心节点     |
                    +----+----+----+-----+
                         |    |    |
                    +----+  +-+-+  +----+
                    |       |   |       |
               北京边缘  上海边缘  广州边缘  ...
                    |       |           |
                 用户A    用户B        用户C

1.2 CDN 的请求流程

步骤1: 用户请求 static.example.com/logo.png

步骤2: DNS 解析
  用户 --> DNS --> CNAME 指向 CDN 的域名
  CDN的智能DNS根据用户IP选择最近的边缘节点
  返回边缘节点 IP: 1.2.3.4

步骤3: 用户请求到达边缘节点

步骤4: 缓存命中判断
  ├── 缓存命中(HIT): 直接返回缓存内容 (最快)
  └── 缓存未命中(MISS): 回源获取

步骤5: 回源(如果需要)
  边缘节点 --> CDN中心节点 --> 源站
  获取内容后缓存到边缘节点
  返回给用户

后续请求: 其他北京用户请求同一资源
  --> 边缘节点缓存命中 --> 直接返回,无需回源

1.3 CDN 适合缓存什么

适合CDN缓存(静态内容):          不适合CDN缓存(动态内容):
├── 图片 (PNG, JPG, WebP)       ├── API 响应(个性化数据)
├── CSS / JavaScript 文件        ├── 用户认证信息
├── 字体文件                     ├── 实时数据(股票行情)
├── 视频 / 音频文件              └── 搜索结果
└── PDF / 文档

1.4 CDN 缓存策略

缓存控制头部:
  Cache-Control: public, max-age=31536000   -- CDN缓存1年
  加上文件指纹: /logo.abc123.png            -- 内容变化时文件名变化

回源控制:
  CDN边缘节点 --> If-None-Match: "etag" --> 源站
  源站 304 Not Modified --> 边缘节点继续使用缓存
  源站 200 新内容 --> 边缘节点更新缓存

缓存刷新:
  通过 CDN 控制台或 API 主动清除边缘缓存
  场景: 紧急修复错误的资源文件

🤔 想一想 如果你的网站在全国有用户,但源站只在深圳,不使用 CDN 时北方用户的体验会怎样?使用 CDN 后呢?


二、负载均衡:分散压力的艺术

2.1 为什么需要负载均衡

单台服务器的处理能力有限。当并发请求超过其承载极限时,就需要多台服务器分担负载:

没有负载均衡:
  所有请求 -----> 单台服务器 (容易过载崩溃)

有负载均衡:
  所有请求 -----> 负载均衡器 ----+---> 服务器1
                                 +---> 服务器2
                                 +---> 服务器3
                                 均匀分配流量

2.2 四层负载均衡(L4 LB)

工作在传输层,根据 IP 地址和端口号分配流量,不理解 HTTP 内容:

四层负载均衡工作方式:

客户端 --> [src: 1.1.1.1:5000, dst: VIP:80]
              |
              v
        L4 负载均衡器
        (只看 IP + 端口)
              |
    +---------+---------+
    |         |         |
  后端1     后端2     后端3

转发方式:
- NAT 模式: 修改目的 IP 为后端服务器 IP
- DR 模式 (Direct Return): 不修改 IP,后端直接回复客户端
- IP 隧道模式: 使用 IP-in-IP 封装转发

代表产品: LVS (Linux Virtual Server), F5, 云厂商 NLB

2.3 七层负载均衡(L7 LB)

工作在应用层,能够解析 HTTP 内容,做出更精细的路由决策:

七层负载均衡工作方式:

客户端 --> GET /api/users HTTP/1.1
           Host: api.example.com
              |
              v
        L7 负载均衡器
        (解析HTTP内容)
              |
    根据请求内容路由:
    /api/*    --> API 服务器集群
    /static/* --> 静态资源服务器
    /ws/*     --> WebSocket 服务器

高级功能:
- 基于 URL 路径路由
- 基于 Host 头路由(多租户)
- 基于 Cookie/Header 路由(会话保持)
- SSL 终止(在 LB 处理 TLS)
- 请求重写和限流

代表产品: Nginx, HAProxy, Envoy, 云厂商 ALB

2.4 负载均衡算法

算法原理适用场景
轮询(Round Robin)依次分配给每台服务器服务器配置相同
加权轮询按权重比例分配服务器配置不同
最少连接分配给当前连接数最少的服务器请求处理时间差异大
IP 哈希根据客户端 IP 计算哈希值需要会话保持
一致性哈希减少服务器增减时的重新分配缓存场景

2.5 L4 vs L7 对比

+------------------+------------------+------------------+
|    特性           |    四层 (L4)      |    七层 (L7)      |
+------------------+------------------+------------------+
| 工作层次          | 传输层            | 应用层            |
| 理解HTTP          | 否               | 是                |
| 性能              | 极高(百万级QPS)   | 较高(十万级QPS)    |
| 功能              | 简单转发          | 丰富的路由策略     |
| SSL终止           | 不支持/透传       | 支持              |
| 会话保持          | IP哈希            | Cookie/Header    |
| URL路由           | 不支持            | 支持              |
| 典型场景          | 高并发TCP转发     | Web应用路由       |
+------------------+------------------+------------------+

三、连接优化

3.1 连接池

频繁创建和销毁 TCP 连接(包括 TLS 握手)开销很大。连接池预先建立并维护一组连接,供请求复用:

没有连接池:
  请求1: TCP握手 + TLS握手 + 发送 + 接收 + 断开  (150ms开销)
  请求2: TCP握手 + TLS握手 + 发送 + 接收 + 断开  (150ms开销)
  请求3: TCP握手 + TLS握手 + 发送 + 接收 + 断开  (150ms开销)

使用连接池:
  初始化: 预建3个连接 (一次性开销)
  请求1: 从池中取连接 + 发送 + 接收 + 归还  (5ms开销)
  请求2: 从池中取连接 + 发送 + 接收 + 归还  (5ms开销)
  请求3: 从池中取连接 + 发送 + 接收 + 归还  (5ms开销)

连接池参数:
  - 最小空闲连接数: 保持的最少连接(预热)
  - 最大连接数: 不超过后端服务器的承载能力
  - 空闲超时: 长时间未使用的连接回收
  - 连接寿命: 定期更换连接,避免使用过期连接

3.2 HTTP Keep-Alive

HTTP/1.1 默认开启持久连接,在同一个 TCP 连接上串行发送多个请求:

Nginx 配置示例:
  keepalive_timeout 65;       # 空闲超时65秒
  keepalive_requests 1000;    # 单个连接最多处理1000个请求

上游连接池:
  upstream backend {
      server 10.0.0.1:8080;
      server 10.0.0.2:8080;
      keepalive 32;           # 每个worker保持32个空闲连接
  }

3.3 TCP 优化参数

Linux 内核参数调优:

# 开启 TCP Fast Open (减少握手延迟)
net.ipv4.tcp_fastopen = 3

# 调整连接队列大小(防止 SYN Flood 丢连接)
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535

# TIME_WAIT 优化
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30

# 窗口缩放(高带宽延迟场景)
net.ipv4.tcp_window_scaling = 1

# 拥塞控制算法
net.ipv4.tcp_congestion_control = bbr

四、WebSocket:全双工通信

4.1 HTTP 轮询的局限

传统 HTTP 是请求-响应模式,服务器不能主动推送数据。为了实现”实时”效果,早期方案是轮询:

短轮询(Short Polling):
  客户端: 有新消息吗? --> 服务器: 没有
  (1秒后)
  客户端: 有新消息吗? --> 服务器: 没有
  (1秒后)
  客户端: 有新消息吗? --> 服务器: 有!这是新消息
  问题: 大量无效请求,浪费带宽和服务器资源

长轮询(Long Polling):
  客户端: 有新消息吗? --> 服务器: (不立即回复,挂起请求)
  (等待30秒或有新消息)
  服务器: 有新消息! --> 客户端: 收到! 立即发起新的长轮询
  改进: 减少无效请求,但仍需频繁建立连接

4.2 WebSocket 的工作原理

WebSocket 在 HTTP 的基础上升级为全双工通信协议:

WebSocket 握手(基于 HTTP Upgrade):

客户端:
  GET /chat HTTP/1.1
  Host: server.example.com
  Upgrade: websocket              <-- 请求升级协议
  Connection: Upgrade
  Sec-WebSocket-Key: dGhlIHNhbX...
  Sec-WebSocket-Version: 13

服务器:
  HTTP/1.1 101 Switching Protocols    <-- 同意升级
  Upgrade: websocket
  Connection: Upgrade
  Sec-WebSocket-Accept: s3pPLMBiTx...

握手完成后:
  客户端 <=====全双工WebSocket通道=====> 服务器
  双方都可以随时主动发送消息
WebSocket vs HTTP 对比:

HTTP:
  客户端 --> 请求 --> 服务器
  客户端 <-- 响应 <-- 服务器
  (每次通信都需要请求-响应)

WebSocket:
  客户端 --> 消息 --> 服务器  (随时)
  客户端 <-- 消息 <-- 服务器  (随时)
  客户端 --> 消息 --> 服务器  (随时)
  (建立连接后双方自由通信)

4.3 WebSocket 适用场景

适合 WebSocket:                  不适合 WebSocket:
├── 即时通讯(聊天室)            ├── 普通网页浏览
├── 实时数据推送(股票行情)       ├── RESTful API 调用
├── 协同编辑(在线文档)          ├── 文件上传下载
├── 在线游戏状态同步             └── 低频更新的数据
└── 实时通知系统

五、其他性能优化技术

5.1 数据压缩

HTTP 压缩:
  客户端: Accept-Encoding: gzip, deflate, br
  服务器: Content-Encoding: br  (Brotli, Google开发, 典型场景下比gzip压缩率高20-25%)

压缩效果对比 (典型HTML文件):
  原始:   100 KB
  gzip:   25 KB  (压缩率 75%)
  brotli: 20 KB  (压缩率 80%)

5.2 资源合并与拆分

HTTP/1.1 时代的优化:
  - 合并小文件: 多个CSS/JS合并为一个(减少请求数)
  - 雪碧图: 多个小图标合并为一张大图
  - 内联小资源: 小图片转为 Base64 嵌入 HTML

HTTP/2 时代的变化:
  - 多路复用使得合并不再必要
  - 反而应该拆分: 独立文件利于缓存(只更新变化的文件)
  - 雪碧图可以被单独图标替代

5.3 DNS 预解析与预连接

<!-- DNS 预解析: 提前解析第三方域名 -->
<link rel="dns-prefetch" href="//cdn.example.com">

<!-- 预连接: 提前建立 TCP + TLS 连接 -->
<link rel="preconnect" href="https://api.example.com">

<!-- 预加载: 提前下载关键资源 -->
<link rel="preload" href="/critical.css" as="style">

<!-- 预获取: 空闲时下载后续页面可能需要的资源 -->
<link rel="prefetch" href="/next-page.js">

5.4 性能指标与监控

核心 Web 指标 (Core Web Vitals):

LCP (Largest Contentful Paint) -- 最大内容绘制
  衡量主要内容多快可见
  目标: < 2.5 秒

INP (Interaction to Next Paint) -- 交互到下次绘制
  衡量页面整体交互响应速度(2024年3月正式取代 FID)
  目标: < 200 毫秒

CLS (Cumulative Layout Shift) -- 累积布局偏移
  衡量视觉稳定性
  目标: < 0.1

TTFB (Time to First Byte) -- 首字节时间
  从请求到收到第一个字节的时间
  目标: < 800 毫秒
  受影响因素: DNS解析 + TCP握手 + TLS握手 + 服务器处理

🤔 想一想 一个电商网站首页加载慢,你会从哪些方面入手排查和优化?请列出至少 5 个可能的优化点。


六、章节小结

  1. CDN 通过在全球分布边缘节点缓存静态内容,大幅减少用户访问延迟。
  2. 负载均衡分为四层(基于 IP/端口)和七层(基于 HTTP 内容),各有适用场景。
  3. 连接池和 Keep-Alive 复用 TCP 连接,避免重复握手开销。
  4. WebSocket 提供全双工通信能力,适用于需要实时数据推送的场景。
  5. 数据压缩、资源优化、DNS 预解析等多种技术协同作用,提升整体 Web 性能。

📝 结尾自测:检验你的收获

  1. CDN 是如何通过 DNS 将用户请求引导到最近的边缘节点的?
  2. 四层负载均衡和七层负载均衡各自的优缺点是什么?什么场景下选择哪种?
  3. 连接池的核心参数有哪些?设置不当会导致什么问题?
  4. WebSocket 的握手过程是怎样的?它和 HTTP 长轮询相比有什么优势?
  5. 你知道 Core Web Vitals 包含哪些指标吗?每个指标衡量的是什么?

下一章预告:理解了网络协议和性能优化的原理之后,是时候动手写代码了。下一章我们将从最底层的 Socket 编程出发,一步步上升到 RESTful API 设计和 gRPC 高性能通信框架,把网络知识转化为实战能力。

购买课程解锁全部内容

网络通信第一课:10 章掌握计算机网络

¥29.90