|
|
|
@ -7,6 +7,14 @@ TCP 的构成 |
|
|
|
|
1. 前言 |
|
|
|
|
2. 三次握手 |
|
|
|
|
3. 拥塞预防及控制 |
|
|
|
|
* 流量控制 |
|
|
|
|
* 慢启动 |
|
|
|
|
* 拥塞预防 |
|
|
|
|
4. 队首阻塞 |
|
|
|
|
5. 针对 TCP 的优化建议 |
|
|
|
|
* 服务器配置调优 |
|
|
|
|
* 应用程序行为调优 |
|
|
|
|
* 性能检查清单 |
|
|
|
|
|
|
|
|
|
#### 前言 |
|
|
|
|
|
|
|
|
@ -68,3 +76,122 @@ HTTP 标准并未规定 TCP 就是唯一的传输协议。如果你愿意,还 |
|
|
|
|
|
|
|
|
|
要说明这种动态适应机制的好处,可以想象你在家观看一个大型的流视频。视频服务器会尽最大努力根据你的下行连接提供最高品质信息。而此时,你家里又有人打开一个新连接下载某个软件的升级包。可供视频流使用的下行带宽一下子少了很多,视频服务器必须调整它的发送速度。否则,如果继续保持同样的速度,那么数据很快就会在某个中间的网关越积越多,最终会导致分组被删除,从而降低网络传输效率。 |
|
|
|
|
|
|
|
|
|
要理解慢启动,最好看一个例子。同样,假如纽约有一个客户端,尝试从位于伦敦的服务器上取得一个文件。首先,三次握手,而且在此期间双方各自通过 ACK 分组通告自己的接收窗口(rwnd)大小。在发送完最后一次 ACK 分组后,就可以交换应用数据了。 |
|
|
|
|
|
|
|
|
|
此时,根据交换数据来估算客户端与服务器之间的可用带宽是唯一的方法,而且这也是慢启动算法的设计思路。首先,服务器通过 TCP 连接初始化一个新的拥塞窗口(cwnd)变量,将其值设置为一个系统设定的保守值(在 Linux 中就是 initcwnd)。 |
|
|
|
|
|
|
|
|
|
拥塞窗口大小(cwnd): |
|
|
|
|
|
|
|
|
|
发送端对从客户端接收确认(ACK)之前可以发送数据量的限制。 |
|
|
|
|
|
|
|
|
|
发送端不会通过 cwnd 变量,即发送端和接收端不会交换这个值。此时,位于伦敦的服务器只是维护这么一个私有变量。此时又有了一条新规则,即客户端与服务器之间最大可以传输(未经 ACK 确认的)数据量取 rwnd 和 cwnd 变量中的最小值。那服务器和客户端怎么确定拥塞窗口大小的最优值呢?毕竟,网络状况随时都在变化,即使相同的两个网络节点之间也一样。如果能通过算法来确定每个连接的窗口大小,而不用手工调整就最好了。 |
|
|
|
|
|
|
|
|
|
解决方案就是慢启动,即在分组被确认后增大窗口大小,慢慢的启动! |
|
|
|
|
|
|
|
|
|
新 TCP 连接传输的最大数据量取 rwnd 和 cwnd 中的最小值,而服务器实际上可以向客户端发送 4 个 TCP 段,然后就必须停下来等待确认。此后,每收到一个 ACK,慢启动算法就会告诉服务器可以将它的 cwnd 窗口增加 1 个 TCP 段。每次收到 ACK 后,都可以多发送两个新的分组。TCP 连接的这个阶段通常被称为 “指数增长” 阶段,因为客户端和服务器都在向两者之间网络路径的有效带宽迅速靠拢。 |
|
|
|
|
|
|
|
|
|
![](https://i.loli.net/2019/06/20/5d0af8e0edfb940685.png) |
|
|
|
|
|
|
|
|
|
为什么知道有个慢启动对我们构建浏览器应用这么重要呢?因为包括 HTTP 在内的很多应用层协议都运行在 TCP 之上,无论带宽多大,每个 TCP 连接都必须经过慢启动阶段。换句话说,我们不可能一上来就完全利用连接的最大带宽! |
|
|
|
|
|
|
|
|
|
慢启动导致客户端与服务器之间经过几百 ms 才能达到接近最大速度的问题,对于大型流式下载服务的影响倒不显著,因为慢启动的时间可以分摊到整个传输周期内消化掉。 |
|
|
|
|
|
|
|
|
|
可是,对于很多 HTTP 连接,特别是一些短暂、突发的连接而言,常常会出现还没有达到最大窗口请求就被终止的情况。换句话说,很多 Web 应用的性能经常受到服务器与客户端之间往返时间的制约。因为慢启动限制了可用的吞吐量,而这对于小文件传输非常不利。 |
|
|
|
|
|
|
|
|
|
> 慢启动重启 |
|
|
|
|
> |
|
|
|
|
> 除了调节新连接的传输速度,TCP 还实现了 SSR(Slow-Start Restart,慢启动重启)机制。这种机制会在连接空闲一定时间后重置连接的拥塞窗口。道理很简单,在连接空闲的同时,网络状况也可能发生了变化,为了避免拥塞,理应将拥塞窗口重置回 “安全的” 默认值。 |
|
|
|
|
> |
|
|
|
|
> 毫无疑问,SSR 对于那些会出现突发空闲的长周期 TCP 连接(比如 HTTP 的 keep-alive 连接)有很大的影响。因此,我们建议在服务器上禁用 SSR。 |
|
|
|
|
|
|
|
|
|
##### 拥塞预防 |
|
|
|
|
|
|
|
|
|
认识到 TCP 调节性能主要依赖丢包反馈机制非常重要。换句话说,这不是一个假设命题,而是一个具体何时发生的命题。慢启动以保守的窗口初始化连接,随后的每次往返都会成倍提高传输的数据量,直到超过接收端的流量控制窗口,即系统配置的拥塞阈值窗口,或者有分组丢失为止,此时拥塞预防算法介入。 |
|
|
|
|
|
|
|
|
|
拥塞预防算法把丢包作为网络拥塞的标志,即路径中某个连接或路由器已经拥堵了,以至于必须采取删包措施。因此,必须调整窗口大小,以避免造成更多的包丢失,从而保证网络畅通。 |
|
|
|
|
|
|
|
|
|
重置拥塞窗口后,拥塞预防机制按照自己的算法来增大窗口以尽量避免丢包。某个时刻,可能又会有包丢失,于是这个过程再从头开始。如果你看过 TCP 连接的吞吐量跟踪曲线,发现该曲线呈锯齿状,到现在就该明白为什么了。这是拥塞控制和预防算法在调整拥塞窗口,进而消除网络中的丢包问题。 |
|
|
|
|
|
|
|
|
|
#### 队首阻塞 |
|
|
|
|
|
|
|
|
|
TCP 在不可靠的信道上实现了可靠的网络传输。基本的分组错误检测与纠正、按序交互、丢包重发,以及保证网络最高效率的流量控制、拥塞控制和预防机制,让 TCP 成为大多数网络应用中最常见的传输协议。 |
|
|
|
|
|
|
|
|
|
虽然 TCP 很流行,但它并不是唯一的选择,而且在某些情况下也不是最佳选择。特别是按序交互和可靠交互有时候并不必要,反而会导致额外的延迟,对性能造成负面影响。 |
|
|
|
|
|
|
|
|
|
要理解为什么,可以想一想,每个 TCP 分组都会带着一个唯一的序列号被发出,而所有分组必须按顺序传送到接收端(如下图)。如果中途有一个分组没能到达接收端,那么后续分组必须保存到接收端的 TCP 缓冲区,等待丢失的分组重发并到达接收端。这一切都发生在 TCP 层,应用程序对 TCP 重发和缓冲区中排队的分组一无所知,必须等待分组全部到达才能访问数据。在此之前,应用程序只能在通过套接字读数据时感觉到延迟交互。这种效应称为 TCP 的队首阻塞。 |
|
|
|
|
|
|
|
|
|
![](https://i.loli.net/2019/06/20/5d0b1e1f372ee20894.png) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
队首阻塞造成的延迟可以让我们的应用程序不用关心重排和重组,从而让代码保持简洁。然后,代码简洁也要付出代价,那就是分组到达时间会存在无法预知的延迟变化。这个时间变化通常被称为抖动,也是影响应用程序性能的一个主要因素。 |
|
|
|
|
|
|
|
|
|
另外,有些应用程序可能并不需要可靠的交付或者不需要按顺序交付。比如,每个分组都是独立的消息,那么按顺序交付就没有任何必要。而且,如果每个消息都会覆盖之前的消息,那么可靠交付同样也没有必要了。可惜的是,TCP 不支持这种情况,所有分组必须按顺序交付。 |
|
|
|
|
|
|
|
|
|
无需按序交付数据或能够处理分组丢失的应用程序,以及对延迟或抖动要求很高的应用程序,最好选择 UDP 等协议。 |
|
|
|
|
|
|
|
|
|
>丢包就丢包 |
|
|
|
|
> |
|
|
|
|
>事实上,丢包是让 TCP 达到最佳性能的关键。被删除的包恰恰是一种反馈机制,能够让接收端和发送端各自调整速度,以避免网络拥堵,同时保持延迟最短。另外,有些应用程序可以容忍丢失一定数量的包,比如语言和游戏状态通信,就不需要可靠传输或按序交付。 |
|
|
|
|
|
|
|
|
|
#### 针对 TCP 的优化建议 |
|
|
|
|
|
|
|
|
|
TCP 是一个自适应的,对所有网络节点一视同仁的、最大限制利用底层网络的协议。因此,优化 TCP 的最佳途径就是调整它感知当前网络状况的方式,根据它之上或之下的抽象层的类型和需求来改变它的行为。 |
|
|
|
|
|
|
|
|
|
不同应用程序需求间的复杂关系,以及每个 TCP 算法中的大量因素,使得 TCP 调优成为学术和商业研究的一个无底洞。本章只蜻蜓点水般地介绍了影响 TCP 性能的几个典型因素,而没有探讨的选择性应答、延迟应答、快速转发等,随便一个都能让你领略到 TCP 的复杂性,感受到理解、分析和调优之难。 |
|
|
|
|
|
|
|
|
|
尽管如此,而且每个算法和反馈机制的具体细节可能会继续发展,但核心原理以及它们的影响是不变的: |
|
|
|
|
|
|
|
|
|
* TCP 三次握手增加了整整一次往返时间 |
|
|
|
|
* TCP 慢启动将被应用到每个新连接 |
|
|
|
|
* TCP 流量及拥塞控制会影响所有连接的吞吐量 |
|
|
|
|
* TCP 的吞吐量由当前拥塞窗口大小控制 |
|
|
|
|
|
|
|
|
|
结果,现代高速网络中 TCP 连接的数据传输速度,往往会受到接收端和发送端之间往返时间的限制。另外,尽管带宽不断增长,但延迟依旧受限于光速,而且已经限定在了其最大值的一个很小的常数因子之内。大多数情况下,TCP 的瓶颈都是延迟,而非带宽。 |
|
|
|
|
|
|
|
|
|
##### 服务器配置调优 |
|
|
|
|
|
|
|
|
|
一句话,让你的服务器跟上时代是优化发送端和接收端 TCP 栈的首要措施。 |
|
|
|
|
|
|
|
|
|
有了最新的内核,我们推荐你遵循如下最佳实践来配置自己的服务器。 |
|
|
|
|
|
|
|
|
|
1. 增大 TCP 的初始拥塞窗口 |
|
|
|
|
|
|
|
|
|
加大起始拥塞窗口可以让 TCP 在第一次往返就传输较多数据,而随后的速度提升也会很明显。对于突发性的短暂连接,这也是特别关键的一个优化。 |
|
|
|
|
|
|
|
|
|
2. 慢启动重启 |
|
|
|
|
|
|
|
|
|
在连接空闲时禁用慢启动可以改善瞬时发送数据的长 TCP 连接的性能。 |
|
|
|
|
|
|
|
|
|
3. 窗口缩放 |
|
|
|
|
|
|
|
|
|
启用窗口缩放可以增大最大接收窗口大小,可以让高延迟的连接达到更好吞吐量。 |
|
|
|
|
|
|
|
|
|
4. TCP 快速打开 |
|
|
|
|
|
|
|
|
|
在某些条件下,允许在第一个 SYN 分组中发送应用程序数据。TFO(TCP Fast Open,TCP 快速打开)是一种新的优化选项,需要客户端和服务器共同支持。为此,首先要搞清楚你的应用程序是否可以利用这个特性。 |
|
|
|
|
|
|
|
|
|
以上几个设置再加上最新的内核,可以确保最佳性能:每个 TCP 连接都会具有较低的延迟和较高的吞吐量。 |
|
|
|
|
|
|
|
|
|
##### 应用程序行为调优 |
|
|
|
|
|
|
|
|
|
调优 TCP 性能可以让服务器和客户端之间达到最大吞吐量和最小延迟。而应用程序如何使用新的或已经建立的 TCP 连接同样也有很大的关系。 |
|
|
|
|
|
|
|
|
|
* 在快也快不过什么也不用发送,能少发就少发 |
|
|
|
|
* 我们不能让数据传输的更快,但可以让它们传输的距离更短 |
|
|
|
|
* 重用 TCP 连接是提升性能的关键 |
|
|
|
|
|
|
|
|
|
当然,消除不必要的数据传输本身就是很大的优化。比如,减少下载不必要的资源,或者通过压缩算法把要发送的比特数降到最低。然后,通过在不同的地区部署服务器(比如,使用 CDN),把数据放到接近客户端的地方,可以减少网络往返的延迟,从而显著提升 TCP 性能。最后,尽可能重用已经建立的 TCP 连接,把慢启动和其他拥塞控制机制的影响降到最低。 |
|
|
|
|
|
|
|
|
|
##### 性能检查清单 |
|
|
|
|
|
|
|
|
|
优化 TCP 性能的回报是丰厚的,无论什么应用,性能提升可以在服务器的每个连接中体现出来。下面几条请大家务必记在自己的日程表里: |
|
|
|
|
|
|
|
|
|
* 把服务器内核升级到最新版本 |
|
|
|
|
* 确保 cwnd 大小为 10 |
|
|
|
|
* 禁用空闲后的慢启动 |
|
|
|
|
* 确保启动窗口缩放 |
|
|
|
|
* 减少传输冗余数据 |
|
|
|
|
* 压缩要传输的数据 |
|
|
|
|
* 把服务器放到离用户近的地方以减少往返时间 |
|
|
|
|
* 尽最大可能重用已经建立的 TCP 连接 |