前言
TCP 作为一种最常用的传输层协议,它的作用是在不可靠的传输信道上,提供可靠地数据传输。在各层网络协议中,只要有一层协议是可靠的,那么整个网络传输就是安全可靠的。现实中,几乎所有的 HTTP 流量都是经过 TCP 传输。因此,我们要进行 web 性能优化,TCP 是其中的关键一环。要针对 TCP 进行性能优化,就得理解其工作原理。
三次握手
众所周知,建立一次 TCP 连接需要进行三次握手。关于三次握手,一图胜千言。
三次握手给 TCP 带来了很大的延迟,不过这个握手过程是必不可少的。因为如果没有三次握手,有可能会出现一些已经失效的请求包突然又传到服务端,服务端认为这是客户端发起的一次新的连接,于是发出确认包,表示同意建立连接。而客户端并不会有响应,导致服务器出现空等,白白浪费服务器资源。
既然三次握手的过程不可避免,那么我们只能通过重用 TCP 连接,减少三次握手的次数。HTTP 1.1 引入了长连接,通过在请求头中加入 Connection: keep-alive, 来告诉请求响应完毕后,不要关闭连接。不过 HTTP 长连接也是有限制的,服务器通常会设置 keep-alive 超时时间和最大请求数,如果请求超时或者超过最大请求数,服务器会主动关闭连接。
除此之外,TFO(TCP Fast Open,TCP 快速打开)这种机制也被设计用于优化三次握手过程。它通过握手开始时的 SYN 包中的 TFO cookie(一个 TCP 选项)来验证一个之前连接过的客户端。如果验证成功,它可以在三次握手最终的 ACK 包收到之前就开始发送数据。
Linux 3.7 及以后的内核在客户端及服务器中支持 TFO, 对于移动端,Android 和 iOS 9+ 都支持 TFO,不过 iOS 并未默认启用。
PS: 推荐大家装一个 wireshark,可以非常直观的观察到三次握手的过程。
流量控制
流量控制是一种预防发送端向接受端发送过多数据的机制。它的主要目的是为了防止接收端服务过载,从而出现丢包。为了实现流量控制,TCP 连接的每一方都要声明自己的通告窗口(rwnd),表示自己的缓冲区最多能接收多少数据。如果其中一端跟不上对方的发送速度,就通知对方一个较小的窗口。如果窗口大小为 0,应用层必须先清空缓冲区,才能继续接收数据。这就是所谓的滑动窗口协议。
大家可能经常遇到这种情况,自己明明是百兆宽带,实际下载速度每秒却只有几M。这种情况有可能就是通告窗口(rwnd)设置的不合理造成的。最初的 TCP 规范分配给接收窗口大小的字段是 16 位,也就是 64KB(2 的 16 次方)。实际上,rwnd 的大小应该由 BDP(带宽延迟积) 而定。BDP(bit) = bandwidth(b/s) * round-trip time(s)。比如一个 100Mbps 的宽带,RTT 是100 ms,那么 BDP = (100 / 8) * 0.1 = 1.25M。此时,要想提高网络传输吞吐量,rwnd 应该为 1.25 M。
为了解决这个问题,TCP 窗口缩放(TCP Window Scaling)出现了,它将窗口大小由 16 位扩展到 32 位。Linux 上自带了缓冲大小调优机制,如下命令,可以查看 Linux 初始窗口大小:
sysctl net.ipv4.tcp_rmem
// 输出 net.ipv4.tcp_rmem = 4096 87380 6291456
// 从左到右一次为最小值、默认值、最大值
慢启动
流量控制机制可以防止发送端和接收端之间的服务过载,但无法防止任何一端向某个网络的发送数据过载,因此还需要一个估算机制,根据网络环境动态改变数据传输速度,这就是慢启动出现的原因。
慢启动为发送方的 TCP 增加了一个窗口:拥塞窗口(congestion window),记为 cwnd。当与另一个网络的主机建立 TCP 连接时,cwnd 初始化为 1 个 TCP 段。每收到一个 ACK,cwnd 就增加一个 TCP 段。发送端取 cwnd 和 rwnd 中的最小值作为发送上限。可以这样理解,拥塞窗口是发送端使用的流量控制,而通告窗口是接收端使用的流量控制。
一开始 cwnd 为 1,发送方只发送一个 mss(最大报文段长度) 大小的数据包,收到 ack 后,cwnd 加 1,cwnd=2。
此时 cwnd 2,则发送方要发送两个 mss 大小的数据包,发送方会收到两个 ack,则 cwnd 会进行两次加一的操作,则也就是 cwnd+2,则
cwnd=4,也就是 cwnd = cwnd*2。
以此类推,每次 rtt 后,cwnd 都会变成上次发送前的 2 倍。因此,cwnd 的大小是呈指数级在递增。
随着 cwnd 的增加,会发送网络过载,此时会出现丢包。一旦发现有这种问题,cwnd 会成倍减少。
为了减少往返次数,初始拥塞窗口的大小设定就尤为重要。默认情况下(RFC 2581),初始 cwnd 为 4 个 MSS。Google 建议将初始窗口改为 10 个 MSS。根据 Google 的研究,90% 的 HTTP 请求数据都在 16KB 以内,约为 10 个 MSS。
总结
本文介绍了 TCP 的部分工作原理,包括三次握手、流量控制、慢启动,并阐述了 TCP 快速打开、窗口缩放、增加初始拥塞窗口大小等优化手段。内容有点偏理论,还是需要多多实践,才能合理掌握各种优化手段。
参考文献: