4

TCP 性能优化浅析

 2 years ago
source link: https://blog.skrskrskrskr.com/article/TCP%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E6%B5%85%E6%9E%90/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

TCP 性能优化浅析

Feb 02 2018 网络

TCP 作为一种最常用的传输层协议,它的作用是在不可靠的传输信道上,提供可靠地数据传输。在各层网络协议中,只要有一层协议是可靠的,那么整个网络传输就是安全可靠的。现实中,几乎所有的 HTTP 流量都是经过 TCP 传输。因此,我们要进行 web 性能优化,TCP 是其中的关键一环。要针对 TCP 进行性能优化,就得理解其工作原理。

众所周知,建立一次 TCP 连接需要进行三次握手。关于三次握手,一图胜千言。

图1

三次握手给 TCP 带来了很大的延迟,不过这个握手过程是必不可少的。因为如果没有三次握手,有可能会出现一些已经失效的请求包突然又传到服务端,服务端认为这是客户端发起的一次新的连接,于是发出确认包,表示同意建立连接。而客户端并不会有响应,导致服务器出现空等,白白浪费服务器资源。

既然三次握手的过程不可避免,那么我们只能通过重用 TCP 连接,减少三次握手的次数。HTTP 1.1 引入了长连接,通过在请求头中加入 Connection: keep-alive, 来告诉请求响应完毕后,不要关闭连接。不过 HTTP 长连接也是有限制的,服务器通常会设置 keep-alive 超时时间和最大请求数,如果请求超时或者超过最大请求数,服务器会主动关闭连接。

除此之外,TFO(TCP Fast Open,TCP 快速打开)这种机制也被设计用于优化三次握手过程。它通过握手开始时的 SYN 包中的 TFO cookie(一个 TCP 选项)来验证一个之前连接过的客户端。如果验证成功,它可以在三次握手最终的 ACK 包收到之前就开始发送数据。
图2

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 的大小是呈指数级在递增。
图3

随着 cwnd 的增加,会发送网络过载,此时会出现丢包。一旦发现有这种问题,cwnd 会成倍减少。
图4

为了减少往返次数,初始拥塞窗口的大小设定就尤为重要。默认情况下(RFC 2581),初始 cwnd 为 4 个 MSS。Google 建议将初始窗口改为 10 个 MSS。根据 Google 的研究,90% 的 HTTP 请求数据都在 16KB 以内,约为 10 个 MSS。

本文介绍了 TCP 的部分工作原理,包括三次握手、流量控制、慢启动,并阐述了 TCP 快速打开、窗口缩放、增加初始拥塞窗口大小等优化手段。内容有点偏理论,还是需要多多实践,才能合理掌握各种优化手段。


参考文献:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK