你好,我是胜辉。

在上节课里,我们通过一个MTU引发问题的案例,学习了MTU相关的知识点,了解了MTU、MSS、TCP分段、IP分片这些概念之间的区别和联系。我们也学习了用iptables修改MSS来达到规避MTU问题的目的,是不是觉得网络虽然学起来不简单,但学会了好像也挺好玩的?

那这个感觉就对了,学习本来就是一件有意思的事情,也是很有收获感的事情。学得更多,收益更多,反过来就能推动自己继续学得更多,形成良性循环。

当然,上节课我们主要说的是如果传输失败会怎么样,而从这节课开始,我们会讨论讨论传输起来以后的效率问题,比如传输速度。

传输速度是网络性能中非常重要的一部分内容,因为它直接影响了我们享受到的网络服务的品质。比如现在移动端网络越来越快,所以无论传输大文件还是实时的视频通话,都得到了很大的发展。

数据中心更是如此。服务器的网卡早就从多年前的千兆网卡(1Gbps)升级到万兆网卡(10Gbps)。而负载均衡、防火墙等网络设备,更是从10Gbps升级到40Gbps,乃至100Gbps。

在这个大背景下,照理说在数据中心之间传个文件早已不在话下。但是我们偏偏遇到了这样一个奇葩的问题:数据中心间传输文件的速度居然只有400多KB/s,是不是很奇怪?你有没有遇到过类似的传输速度方面的问题,你又是如何通过网络分析,找到根因的呢?

所以这节课,我们就来研究分析下这个文件传输速度的案例,一起来深入探讨下如何提升TCP传输速度的话题。

这样,当你日后也遇到传输速度方面的问题时,就可以运用这节课学到的知识,去真正解决问题。甚至,即使看起来没有速度问题,你也能主动发现提高网络性能的机会,带来额外的惊喜,同时也把你的网络维护的能力提升到一个更高的层次上来。

案例背景

这是2017年我处理的eBay内部的案例。当时我们有一个需求是把一个Firmware安装文件从美国数据中心拷贝到欧洲的一个POP点,这个点在荷兰的阿姆斯特丹。文件不算大,95MB。其实这是一项常规任务,因为我们每年都会做一到两次的Firmware升级,经常在美国几个数据中心之间拷贝安装文件,一般这样一个100MB左右的文件约二十多秒就能完成。

这一次,我们需要把全球POP节点的设备也进行升级。对我们来说,确实也是第一次做从北美到欧洲这么长距离的文件传输,结果发现传输速度慢了一个数量级。我们的POP点有很多个,按顺序做升级的话,每次文件传输都增加3分钟,那么20个POP就增加60分钟,显著拖累了整个的升级速度。

当然,地理上看,从美国加州到荷兰阿姆斯特丹确实极为遥远,距离数千公里,坐飞机也要14个多小时:

图片

然后我们也来看一下美国不同的数据中心之间拷贝的速度:

再看一下从美国拷贝到荷兰的速度:

当你看到两种场景下传输同一个文件的速度有这么大的差别,第一感觉是什么呢?你觉得会是什么因素导致了速度变这么慢?会不会想到带宽?

确实,当时我们也才开始维护欧洲POP点,对美国和欧洲之间的底层传输网络情况并不熟悉,所以也怀疑:会不会是带宽本身就不大呢?我们找了负责骨干网的同事,了解到这个带宽有10Gbps,远超这个传输速度。

然后,你会想到什么呢?对了,我补充一下:这个传输是scp拷贝,所以是基于TCP的。你自然明白,TCP的传输速度也受丢包的影响。如果丢包严重,传输速度肯定跑不起来。我们觉得这个也是有可能的,就去抓包检查。结果发现丢包率并不高,不高的丢包率却导致如此低的速度,这个可能性很小。

更为重要的是,既然我们要排查传输速度的问题,那么首先要看的,是不是网络I/O的状况呢?

补充:抓包示例文件已经传输至Gitee,建议用Wireshark打开示例文件,结合文稿学习,效果更好。

在Wireshark的众多统计工具中,有两个工具就是I/O分析而生的,一个是 I/O Graph,另一个是 TCP Stream Graph。我们先看I/O Graph。

I/O Graph

这个工具在Statistics下拉菜单中:

图片

点击I/O Graph后,Wireshark会弹出一个趋势图,其X轴是时间,Y轴是性能指标:

图片

注意,这时的图是不准确的,所以我们需要做一些调整。既然我们关注传输速度,所以就选择All Bytes这个指标项(也是默认选中的那项)作为Y轴,然后修改它的计量单位。很可能你的Wireshark默认选中的是AVG(Y Field),而这并不是我们要关注的字节数。我们可以双击AVG(Y Field)进入编辑模式,把它改为Bytes

图片

很棒!这时我们就能清晰地看到,Wireshark帮助我们计算出来的分时的速度趋势柱状图了,差不多速度在480KB/s上下:

图片

你可能也注意到了,在X时间轴上看,一开始几秒速度比较低,第7秒才达到400KB/s以上。为什么一开始速度这么低呢?其实,这正是TCP慢启动的一个缩影:初始阶段,速度是特别低的,但是会很快爬高

整体来看,这个传输速度还是很“稳定”的。对,一直很“稳定”得低。

TCP Stream Graphs

如果说I/O Graph展示的速度值很容易理解,那么TCP Stream Graphs展示的信息就需要一点TCP的知识来辅助理解了。

还是到Statics下拉菜单,选择TCP Stream Graphs,在子菜单中选择Time sequence (Stevens)。

补充:Stevens这个名字你应该是熟悉的,他就是鼎鼎大名的TCP/IP三卷和Unix网络编程两卷等名著的作者,Richard Stevens。他把网络协议的圣火带到人间,却又早早离开了人间。这个工具以他的名字命名,也代表了大家对他的深深的怀念。

图片

然后就能看到时间为X轴、TCP序列号为Y轴的图了。你应该知道,序列号其实就等于字节数,那么显然,这里这条线的斜率也就是传输速率了。

图片

我们可以自己计算这个斜率(速率) 是多少。比如可以计算10秒和40秒两处的序列号的差距,再除以(40-10)秒,就是速率了。10秒处的序列号是2800092,40秒处是16480292,那么速率就是(16480292-2800092)/(40-10)=456KB/s。

图片

图片

你可能发现了,两个Graph算出来的速度怎么有点差异?一个是480KB/s左右,一个是456KB/s,相差有5%左右。

其实,这是正常的。因为I/O Graph统计的Bytes是二层帧的大小,而TCP Stream Graphs关注的是四层TCP段的大小。后者比前者少了二层到四层的头部。严格来说,TCP Stream Graphs的斜率,只是TCP payload的速率;而I/O Graph展示的,才是我们一般谈论的传输速度。当然,在定性的讨论中,这点差异是可以忽略的。

理解与传输相关的知识点

其实,上面用了两种方式查证速度,也无非是印证了我们在scp命令输出里,看到的速度值本身是准确的。但目前为止,我们还没有找到造成这个速度的原因。这时候,我们稍微调整一下问题描述:假如带宽足够,网络也稳定,那又是什么决定了TCP传输的速度?

回想一个小学时就学过的公式:速度=距离/时间

看上去极为简单的公式,似乎在这里没什么帮助。不过奥妙的地方在于:什么是距离,什么又是时间?搞清楚了这两个问题的答案,就搞清楚了这次传输分析的精髓。不过我这里先卖个关子,先继续把接下来的知识点讲清楚,到后面你也许自己就能悟到答案了。

假设你在开车,开出去100公里,耗时1小时。这是因为你眼观四路、耳听八方,在确保安全的前提下用比较快的速度行驶。如果你蒙上眼睛,那是1米都不敢开的,是不是?

对于网络传输来说,报文发送出去后,发送端本身就“失去视力”了,也就是看不到报文在错综复杂的网络中行走时,到底遇到了什么样的“路况”。那它是继续发送更多数据好呢,还是先等对方确认这部分数据,然后再发送下一份数据好呢?如果确认了,那下一份数据又该发多少呢?一系列问题,需要我们回答。

不过还好,睿智的TCP的设计者们早就考虑到了这些问题。这里呢,有几个相关知识点你需要了解,且听我细细道来。

首先,一个报文被视为成功发送,基于以下的路径:

(开始)发送端>>数据报文>>接收端>> ACK报文>>发送端(结束)

这样一来一回的时间,就是报文被成功传送的耗时。你可能会有疑问:数据报文到了接收端不就是已经完成传输了吗?其实,这个时候发送端还不知道接收端是否已经完成接收了,因为还没收到确认。

操作系统的角度来看:“完成”的标志,是这部分数据可以从发送缓存中删除;从缓存中删除这部分数据的前提,就是收到ACK报文。即“确认多少,我就清除多少”。像下图这样:

来回的时间,在英文里叫 Round Trip Time(RTT),即往返时间,也叫时延。这是你需要知道的第一个名词。

你有没有发现,报文大部分时候就像飞机在空中航行,跟两端的哪一端都碰不着,除了起飞和降落。在“空中”的时间,就取决于RTT。RTT越长,报文在“空中”的时间就越长。这个时候,这些报文就有了一个新的身份,叫做“在途数据”,它的大小,叫做在途字节数。英文是 Bytes in flight。这是你需要知道的第二个名词。

事实上,Wireshark也很周到地帮我们想到了这一点,在TCP详情页的SEQ/ACK analysis部分就有 Bytes in flight 信息。比如下面这个:

图片

带宽,类似道路的车道数,你应该见过节假日里高速公路上停满私家车的图片。可见,车道越多,高速路越长,能容纳的车就越多。在网络世界里,带宽很大、RTT很长的网络,被冠以一个特定的名词,叫做长肥网络,英文是 Long Fat Network。在长肥网络中的TCP连接,叫做长肥管道,英文是 Long Fat Pipeline。这是你需要知道的第三个名词。

道路上最多可以容纳多少辆车呢?显然,是车道数×道路长度。那么类似地,带宽跟往返时间(RTT)相乘,就是在空中飞行的报文的最大数量,即带宽时延积。在英文里叫 Bandwidth Delay Product,缩写是BDP(Delay就是RTT)。不用我说,你也明白带宽时延积是你需要知道的第四个名词。

其实,以上这些名词都是从英文翻译过来的,所以读起来感觉不太像本土词汇,特别是“长肥管道”。你别把“长肥”记成“肥肠”就行了。

我们用图对比一下,就能明显看出,所谓的长肥管道,就是带宽时延积更大的网络,蓝色部分表示的就是带宽时延积:

你可能还会有疑问,在途数据不是应该RTT的一半(即单程时间)再乘以带宽吗?从飞机的比喻来看,也许是的。但是你要考虑下图这种情况。如果回程时间只是用来传输ACK,没有被用来传输实际的数据,效率就打了对折了。就像图的右侧表示的,只有一半的时间在真正传输数据,另外一半时间没有在发送数据:

事实上,发送端并不知道什么时候报文到了接收端,它唯一知道的是什么时候自己收到了ACK报文,所以它会把这些时间全部用来发送报文。我把上图中原先不传数据的时间段2和4也改为发送数据,于是形成了下面这张图:

当然,实际情况是远比示意图复杂的。比如,在途字节数一定等于带宽×RTT吗?要知道,接收端也不傻,也是可以源源不断地对数据进行确认的,这样也就形成了更加复杂的关系:发送端在不断发出在途数据,接收端也在不断回复ACK。这时候的在途数据,可以用下面的公式来获得:

inflight_data = (latest_sequence_sent + latest_len_sent) - max_ack_received

最后还有个重要的概念是“窗口”。有人开玩笑说:讨论TCP握手的时候,大家侃侃而谈;一旦说到窗口,就都沉默了,气氛尴尬。继续往下学习这一部分,你会成为那个打破沉默的人。:)

其实,下次别人跟你切磋“窗口技术”的时候,可以先下手为强:“你说的是到底哪个窗口?”因为事实上,TCP有3个窗口:接收窗口、拥塞窗口,还有发送窗口。

接收窗口是明的,在抓包文件里就能看到;拥塞窗口和发送窗口是暗的,抓包文件里没有。我们看这张图就明白了:

解题

好了,终于把传输相关的关键知识点介绍完了,是不是感觉信息量有点大,甚至还没完全理解?别急,下面我们把这些知识点应用到这个案例中,这些看似干涩的知识点就会逐步丰润起来。

时延

我们首先收集时延信息。用美国数据中心的发送端去Ping阿姆斯特丹接收端的IP,我们发现时延在134ms 左右。用Ping的原因是,它的ICMP报文比较轻量,不会引起双方很多的额外处理时间,所以适合用来获取相对纯粹的网络往返时间。

在TCP通信中,因为协议栈本身也需要做拆解包、缓冲、Socket处理等工作,所以TCP层面的RTT会比IP层面的RTT略长一点。好在,Wireshark也提供了RTT信息。我们选取一个报文,在TCP详情的SEQ/ACK analysis部分,就有iRTT信息。这里是141ms,比Ping探测到的134ms多了一点,这是因为TCP协议栈本身处理也有一些延迟:

图片

iRTT是intial RTT的缩写,Wireshark就是从TCP握手阶段的报文里计算出这个值的。对于客户端来说,就是发出SYN和收到SYN+ACK的间隔。对于服务端,就是发出SYN+ACK和收到ACK的间隔。

带宽时延积

接下来我们算算带宽时延积是多少。时延是134ms,带宽是10Gbps,那么带宽时延积就是0.134×10Gb。转换为Byte需要再除以8,得到约168MB。这个数值的意思是,假设这条链路完全被这次的文件传输连接所占满,那么最多可以有168MB的在途数据。

我们这个Firmware文件也才95MB,如果按这个速度,那一个来回就传完了,也就是只需要134ms!当然,TCP有慢启动机制,不可能一开始就把一百多MB这么大的数字作为初始拥塞窗口,所以也不会真的一个来回就传完了。

到这一步,我们已经明白,带宽时延积并不是限制速度的因素。那么一定是别的东西,在暗地里约束着这次传输。这个东西是什么呢?我们快接近真相了。

发送窗口

在Wireshark里查看接收窗口的数值也是很直观的。任意一个TCP报文的头部都有Window字段,长度为2个字节,所以最大值为2的16次方,即64KB。在第3讲我们讨论TCP握手相关案例时,提到过Window Scale。它是在RFC1323中引进的,使得Window值最大能达到2的30次方,即1GB。

我们看一下当时阿姆斯特丹POP点作为接收端,它宣告给美国数据中心的TCP接收窗口值是多少。为了方便查看,你可以在Wireshark里这样做:

我得到的界面是这样的:

图片

左上角是过滤器,右侧第二列就是刚刚新添加的Calculated window size。显然,能从图中看出,传输起始阶段有不少重传。不过没关系,让我们集中注意力到接收窗口值上。

由图可见,这个阶段的接收窗口是64KB上下浮动,值偏小。照理来说,传输稳定阶段,接收窗口会大很多,也就应该会比64KB大不少吧?我们往下翻,滚动到整体报文的中间位置,看看此时接收窗口值是多少:

图片

有点意外,不仅没有上升,反而偶尔还略微下降了,在54~64KB之间浮动。这是怎么回事?这跟速度慢是否有关系?

前面提到过,发送端实际的发送窗口是拥塞窗口和对方接收窗口这两者中的较小者。那究竟是多少呢?我们可以这么找到它:

图片

主界面里有Bytes in flight这一列后,你就可以排序,下图中显示,这次传输的客户端在途字节数最大是65700,接近64KB,这就是发送窗口。

图片

64KB很小啊,这就相当于,在一个长肥网络中,存在着一个“瘦”很多的管道,完全没有把带宽利用起来。我觉得可以叫做“长瘦管道”。

这个长瘦管道的传输速度是多少呢?在这个案例里,发送窗口=接收窗口,那么在网络上跑的数据量最多就是64KB。那传输这64KB的耗时是多久呢?这不就是往返时间RTT吗?现在,你是否终于回想起前面我卖关子的那个小学公式呢?

速度 = 距离/时间

所以,这次的传输速度的上限就是window/RTT = 64KB/134ms = 478KB/s!还记得前面用TCP Stream Graphs(Stevens)斜线图得到的速率吗?那个是456KB/s。可见,实际值只比上限值略低,说明虽然窗口很小,但传输过程本身还是比较充分地利用了这个有限的窗口。

整个案件得以真相大白:限制速度的最大因素,既不是带宽,也不是丢包,而是窗口,确切地说就是接收端(POP点)的接收窗口。因为这些接收端的设备比较特殊,沿用了老旧的配置,导致TCP接收窗口过小。

窗口小是因为Window Scale没被启用吗?我们检查发现,Window Scale其实启用了,你可以回头看一下窗口值的截图,有几次的值是65728,明确超过了65535。另外,在握手包里也可以看到Window Scale被启用的信息:

图片

红框中的 TCP Option-Windows scale: 6 就是表示窗口系数(Scale Factor)为6,即2的6次方(得64)。也就是说,真正的接收窗口值,是Window字段的值乘以64。

那为什么启用了Window Scale,还是被限制在64KB附近呢?我们发现,这还是受限于这台设备本身的特点。在某些情况下,这里的Window Scale虽然启用了,但无法充分工作,导致实际上这台设备的接收窗口一直被压制在64KB附近。

想象一下,发送端每次“喂到”网络上的数据只有64KB,这64KB还需要经过134ms后才能到达接收端。然后就这样周而复始,直到全部的95MB发送完成。明明是很宽敞的网络,但可惜,对端的收发室太小了,你只好把大货车停车库里,改开电瓶车,一小车一小车地送货,你急也没用。这速度,哪里还起得来?

我们对它的配置做了修改,使得接收窗口大幅增加后,速度马上提上去很多倍,这才彻底解决了问题。

最后的疑问

不过你是否留意到了,难道当时美国本土数据中心里面的设备就没有这个问题吗?如果也是用了这个老旧的配置,难道就没有传输慢的问题吗?

我给你的答案是:其实也是老旧的配置,其实速度也是慢的。但是为什么这个问题就没有被暴露呢?这又是跟前面的知识点有关系了。你能想到是哪个因素掩盖了这个问题吗?

这次的答案不是窗口,而是往返时间。在美国本土的多个数据中心之间,RTT大约在10ms多一点,也就是只有美国到荷兰的134ms的十分之一。联系公式“速度=距离/时间”,分子(接收窗口)不变,分母(时延)变为原来的十分之一,那么速度会变成原来的十倍。十倍的速度就没有显得那么慢了,所以并没有引起我们的注意。

我用一个图表示一下这种RTT的不同引起的传输上的区别:

你看,传输速度的问题,可以说就是窗口和往返时间这两个大玩家在起作用。你只要抓住这两个主要矛盾,就能解决大部分传输速度的问题了。其他因素也可能有它的作用,但一般不是核心矛盾。我们要学会抓重点,这对于工作,乃至对于人生,都是很有意义的。

小结

这节课,我用了一个典型的文件传输的案例,帮你回顾了跟TCP传输有关的方方面面的知识点,包括:

基于以上的知识,我们得以推导出最终的核心公式:速度上限=发送窗口/往返时间。用英文可以表示为:velocity = window/RTT。

以后你在处理TCP传输速度问题的时候,一样可以应用上面这些知识:先获取时延,再定位发送窗口,最后用这个公式去得到速度的上限值。

除了这些TCP的知识点,我也提到了使用Wireshark的一些技巧,包括:

总而言之,你在自己处理类似的传输速度、延迟等问题的时候,一方面可以充分利用我分享的协议方面的知识,获得坚实的理论支撑;一方面也可以使用这节课提到的这些Wireshark分析技巧,获得数据上的支持。当你把理论和数据这两条“腿”都锻炼健壮后,你一定能在网络排查的世界里,更加坚定有力地前行。

思考题

你有没有在工作中遇到过TCP传输速度相关的问题呢?通过这节课的学习,你已经掌握了传输速度相关的不少知识,你准备怎么运用这些知识,来解决这个传输问题呢?

欢迎在留言区分享出你的答案和思考过程,也欢迎你把今天的内容分享给更多的朋友,我们一起成长。

附录

抓包示例文件:https://gitee.com/steelvictor/network-analysis/tree/master/09