TTFB 为什么这么慢

TTFB (Time to First Byte)是指客户端从发起请求到接收到服务器响应的第一个字节的时间,是反应网站性能的一个重要指标 。由于网页的下载时间受到页面体积,客户端带宽等影响更大,TTFB 一般来说能够更好的反应服务端的性能。

精确定义

上面说”从发起请求到接收到服务器响应的第一个字节”仍然有一些模糊,精确一点说,是在完成 DNS 查询、TCP 握手、SSL 握手后 发起 HTTP 请求报文 *到 *接收到服务端第一个响应报文 的时间差距。
image.png

RTT 和 TCP 建连

如果不了解 RTT 和 TCP 建连的耗时,可以看一下 TCP 建连为什么这么慢

TTFB 多少是个合理值

我们拿到 TTFB 这个指标后,最广泛的问题就是 TTFB 究竟应该多少是个合理值?一般来说,我们可以泛泛地认为对于静态页面,50ms 是个非常理想的值(因为大部分情况下 RTT 基本就在这个范围了),而如果超过了 500ms,一般用户就会感觉到明显的等待。

TTFB 如何构成

其实要理解 TTFB 合理的时长,我们可以看一下 TTFB 具体怎么构成。

我们可以使用 tcpdump 抓一下 curl http://www.baidu.com 时发生了什么

1
2
3
4
5
6
7
8
客户端 -> 服务器:seq 3612756767
服务器 -> 客户端:seq 3932881577, ack 3612756768
客户端 -> 服务器:ack 3932881578 // 到这里完成三次握手
客户端 -> 服务器:seq 3612756768:3612756845, length 77: HTTP: GET / HTTP/1.1 // 发送 GET 请求的 HTTP 报文
服务器 -> 客户端:ack 3612756845
服务器 -> 客户端:seq 3932881578:3932883030, ack 3612756845, length 1452: HTTP: HTTP/1.1 200 OK // First Byte 到达
服务器 -> 客户端:seq 3932883030:3932884359, ack 3612756845, win 776, length 1329: HTTP // 继续传输 HTTP 响应报文
// ...

我们看网络的来回可以发现,GET 请求发出后,到收到 First Byte 的时间其实接近于一个 RTT + 后端处理(一般我们叫 ServerRT)的时间。

所以对一个页面的 TTFB 来说,它的时长在通常情况下接近于和服务器的 RTT + ServerRT(可能要多一些协议层的消耗)。

试验环境验证

为了验证这个推论是否正确,我们用一些 RT 基本为零的页面进行验证。其中 Initial Connection 的时间(TCP 握手时间)也是接近 1 RTT 的。所以总体来说 TTFB 应该和 Initial Connection 非常接近。

空页面

准备搞台海外机器实际试试看 TTFB 和 RTT 的差距,空页面是非常接近的

大体积页面

怀疑页面大小是导致协议层的开销增大(会拆成多个 TCP 包),找了个 gzip 后 100kb 的 js,访问一下看看

似乎稍微大了一丢丢,但差距仍然很小,试试看更大的(gzip后仍有 1.x MB)


看到差距仍然非常小,所以说页面的体积对于 TTFB 是基本没有影响的,不会因为回传的 HTTP 报文太大导致首字节传输耗时明显增大。

如何降低 TTFB

那么,当 TTFB 很长时,我们该如何降低 TTFB 呢

减少请求的传输量

例如 cookie 或者 body 很大的 POST 请求,他们的发送会更加耗时。例如当我们尝试把 cookie 变得非常长后抓到的请求变成了:

1
2
3
4
5
6
7
客户端 -> 服务器:seq 2144370637:2144372089: HTTP: GET / HTTP/1.1 // 发送 GET 请求的 HTTP 报文
客户端 -> 服务器:seq 2144372089:2144372988: HTTP // 继续发送,没法送完

服务器 -> 客户端:ack 2144372089
服务器 -> 客户端:ack 2144372988 // 两次 ACK

服务器 -> 客户端 // First Byte 到达

发送请求的 TCP 包直接被拆成了多个,首字节说的是服务端的首字节,客户端发给服务端的包客户端需要接收完整才能做出响应然后返回。

所以我们应该避免在请求中携带过多无用信息。

减少服务端处理时间

这点最容易理解,减少服务端的处理时间(ServerRT),TTFB 自然会下降。

减少 RTT 时间

这点一般没有什么特别好的办法,RTT 是由网络状况和物理位置决定的,想要减少 RTT 就只能在离用户更近的地方增加服务器节点了。

TTFB 越低越好么

先直接放结论,**不是的
优化性能都是为了用户体验,而 TTFB 只是描述某一段过程的参考技术指标。他只所以被看得比较重要,是因为其受影响的因素相对没那么多,能够比较客观的反应服务端的处理时间 + 网络耗时。

TTFB 和 download 的权衡

Gzip

一个很典型的例子,当我们开启 Gzip 时,对于一个比较大的页面 TTFB 必然上涨(压缩需要时间),但是实际上传输的速度要快很多。用户能够更快的看到页面(首字节是无法用于渲染的)。

这种情况下我们不应该去追求 TTFB 的短,真正应该在意的是用户的真实体验。

动态加速

动态加速是个类似的例子,一些 CDN 厂商通过动态加速技术让网页更快的传输。然而由于 CDN 节点会更快的建立连接。导致发起请求的时间被提前了,而 CND 节点则承担了原来和服务器建连的成本,另外 CND 也会在节点中做一些 buffer,这都会导致 TTFB 看起来更长了,然而实际上页面的加载速度是得到了提升的。

单点测试

我们在本地针对 Akamai 的动态加速进行了单点测试

Akamai
1
curl -w "@curl-format.txt" -so ./ppc.html https://23.218.15.80/ppc/mp3.html -H "Host: www.alibaba.com" --insecure
1
2
3
4
5
6
7
8
time_namelookup:  0.004775
time_connect: 0.073131 (TCP handshake)
time_appconnect: 0.232171 (SSL handshake)
time_pretransfer: 0.232290
¦time_redirect: 0.000000
time_starttransfer: 1.401208
----------
time_total: 1.812241

后端 RT:1561455906142 - 1561455905344 = 798

1
2
3
TTFB - RTT - RT =  1169 - 69 - 798 = 300

TTFB - RT = 1169 - 798 = 371

美国统一接入层
1
curl -w "@curl-format.txt" -so ./ppc.html https://205.204.101.142/ppc/mp3.html -H "Host: www.alibaba.com" --insecure
1
2
3
4
5
6
7
8
time_namelookup:  0.005029
time_connect: 0.161610 (TCP handshake)
time_appconnect: 0.806566 (SSL handshake)
time_pretransfer: 0.806884
¦time_redirect: 0.000000
time_starttransfer: 1.830675
----------
time_total: 8.795626


后端 RT = 1561456655052-1561456654266 = 786

1
2
3
TTFB - RTT - RT = 1024 - 156 - 786 = 82

TTFB - RT = 1024 - 786 = 238

单点测试结论

可以看到 Akamai 的动态加速实际上使 TTFB - RT - RTT 的时间更长了(因为 CDN 节点本身的 RTT 短),同时 TTFB - RT 也变长了(CDN 会 buffer 一定量的数据才返回)。然而总体的传输时间从 8.7s 降低到了 1.8s。

这和我们之前对首屏的观测也是一致的,动态加速拉长了 TTFB,但总体首屏是下降的。因为用户是要下载一定有效的内容才能真正进行渲染。


结论

TTFB 是一个非常重要的网站性能指标,能够在前端比较客观的反应出后端的耗时。但是 First Byte 是无法渲染出任何东西的,我们用 TTFB 来侧面衡量网站的后端耗时不代表 TTFB 越短越好

在有些场景下我们使用的一些优化手段让 First Byte 刻意晚了一些(gzip 内容,buffer 一定的内容再开始传输等),这个时候 TTFB 作为一个侧面印证的指标就不是那么的精确了,在这种场景下 TTFB 失去了原本的含义,不需要过于纠结。

如果可能的话,还是把 ServerRT 的耗时尽可能也带到前端来参与统计。

拓展阅读