打开一个网页,前两段(解析、建连)是纯开销,一个字节内容都还没发。 这两段与网页大小无关,所以「首屏慢」往往是它们造成的。
1.10.1 讲的是「经过谁」,这一节讲「时间花在哪」。 这一节的三段拆解,就是前端性能优化的基本原理,剩下都是细节。
一、三段固定开销
把一次网页请求拆成三段计时:
| 阶段 | 做了什么 | 实测 ms |
|---|---|---|
| 域名解析 | 名字 → 一个 IP | 19.6 |
| TCP 建连 | 三次握手接通 | 168.5 |
| 合计 | 还没开始传数据 | 188.2 |
第三行那个「还没开始传数据」是最该记住的一句:这两步是纯开销, 它们的时间只取决于「网络有多远」,与网页有多大完全无关。 所以一个很小的页面也可能打开很慢——慢在开头,不在传输。 注意这两段的量级差:解析 19.6 毫秒、建连 168.5 毫秒, 建连是大头,因为它要一个完整的往返。 而解析为什么快?因为 DNS 结果被本机缓存过——第一次解析通常不会这么便宜。
二、三段各有各的优化
每一段的对策完全不同,不能混着用:
| 阶段 | 怎么优化 | 原理 |
|---|---|---|
| 域名解析 | DNS 预取、本地缓存 | 把 19.6 变成接近 0 |
| TCP 建连 | 长连接、连接池 | 省掉重复的握手 |
| 请求响应 | 压缩、CDN、合并请求 | 减少字节与往返 |
第二行那个「长连接」收益最大:一次握手 168.5 毫秒,一个页面要请求几十个资源,
不保持连接就要握手几十次——这就是 HTTP/1.1 默认 keep-alive 的原因。
也是「连接池」这个词的由来:连接是可以复用的资源,不是一次性的。
第一行那个「DNS 预取」很便宜:在 HTML 里加一行 rel=dns-prefetch,浏览器就会提前解析。
它不保证有用(用户可能不点那个链接),但成本几乎为零。
第三行那个「合并请求」是 HTTP/1.1 时代的特产:
因为 1.1 有「同域并发连接数」限制(通常 6 个),所以要把小文件合并。
HTTP/2 多路复用之后,这个优化反而变成反模式——合并会让缓存粒度变粗。
三、为什么首屏慢
把三段叠起来看,就明白「首屏」卡在哪:
| 环节 | 能不能省 | 省不掉的原因 |
|---|---|---|
| 域名解析 | 能接近 0 | 缓存、预取 |
| TCP 建连 | 能省一次 | 长连接复用 |
| TLS 握手 | 要多一个往返 | 加密必需 |
| 首字节时间 | 省不掉 | 服务端要算 |
第三行是 HTTPS 比 HTTP 多出来的那一段:TLS 握手要多一到两个往返, 所以在高延迟链路上,HTTPS 的首屏会明显更慢。 这不是「加密很慢」——加密的计算开销很小,慢的是「多出来的往返」。 TLS 1.3 把握手压到一个往返,就是在解决这个问题。 第四行那个「服务端要算」是唯一真正省不掉的: 数据库查询、模板渲染,再快也要几毫秒到几百毫秒。 所以「首屏优化」的极限就是「服务端算完第一字节的时间」,网络再怎么优化也突破不了它。
四、动手 Demo:一次完整的互联网通信,时间花在哪
把打开一个网页拆成三段计时:解析、建连、请求响应。 重点是前两段的合计——那是不含任何内容的纯开销。
import socket, time
host = 'www.python.org'
t0 = time.perf_counter()
ip = socket.gethostbyname(host)
dns = (time.perf_counter() - t0) * 1000
t0 = time.perf_counter()
c = socket.create_connection((host, 443), timeout=10)
tcp = (time.perf_counter() - t0) * 1000
c.close()
print('%-10s %-24s %.1f ms' % ('域名解析', ip, dns))
print('%-10s %-24s %.1f ms' % ('TCP 建连', '接通', tcp))
print('%-10s %-24s %.1f ms' % ('合计', '还没开始传数据', dns + tcp))
实跑输出:
--------------------------------------------------------------------
一次完整的互联网通信,时间花在哪
--------------------------------------------------------------------
**把「打开一个网页」拆成三段计时**:**域名解析、TCP 建连、请求响应。**
阶段 做了什么 耗时ms
----------------------------------------------
域名解析 名字 → 151.101.64.223 19.6
TCP 建连 接通 168.5
合计 **还没开始传数据** 188.2
**注意最后一行那个「还没开始传数据」**:**这两步全是纯开销**,一个字节的网页内容都还没发。
**这就是「首屏慢」的典型原因**:**网络越远,这两段固定开销越大,而且与网页大小无关。**
**三段各有各的优化手段**:
阶段 怎么优化
-------------------------------------------------------
域名解析 **DNS 预取、本地缓存、就近的 DNS 服务器**
TCP 建连 **长连接、连接池、就近接入**
请求响应 **压缩、CDN、减少请求数**
**这三条加起来就是前端性能优化的全部基本原理**——**剩下的都是细节。**
解析 19.6 毫秒、建连 168.5 毫秒、合计 188.2 毫秒:建连是大头,因为它要一个完整往返。 而解析这么快,是因为本机缓存过这个域名——第一次解析通常要几十到几百毫秒。 这 188 毫秒里一个字节的网页内容都没发: 这就是「首屏慢」的典型原因——网络越远,这两段固定开销越大,与网页大小无关。 注意这里连的是 443 端口但没有做 TLS 握手:所以这 168.5 毫秒只是 TCP 的往返。 真实的 HTTPS 还要再加一到两个往返——这就是「为什么加了 HTTPS 之后首屏变慢」的直接原因。
30 秒实验
- 把
host换成一个从没访问过的域名,看解析时间变多少。 - 连跑三次,看建连时间是否稳定——第一次往往更慢(要建路由缓存)。
- 用开发者工具的「网络」面板看一个真实页面,数一下「排队/等待」占了多少时间。
常见误区
- 以为网页小就一定快。 前两段是纯开销,与网页大小无关,小页面也可能因为解析与建连而慢。
- 以为 HTTPS 慢是因为「加密计算」。 加密很快,慢的是 TLS 握手多出来的一到两个往返。
- 以为优化就是「压缩图片」。 压缩只作用于第三段,前两段要靠缓存与长连接。
自测三问(点开对答案)
1. 打开网页的前两段固定开销是什么? —— **域名解析与 TCP 建连**,合计约 188 毫秒,这段时间里一个字节的网页内容都还没发。 2. 为什么长连接的收益最大? —— **一次握手约 168 毫秒,一个页面要请求几十个资源**,不保持连接就要握手几十次。 连接是可复用的资源。 3. HTTPS 比 HTTP 慢在哪? —— **慢在 TLS 握手多出来的一到两个往返**,不是加密计算。 所以 TLS 1.3 把握手压到一个往返就是在解决这个问题。下一节讲移动通信——为什么它最难的不是速率,是「切换」。
- ☐ (本节完,下一节 1.10.3 移动通信)