本节书摘来自异步社区《大咖讲Wireshark网络分析》一书中的再来一个很妖的问题,作者林沛满,更多章节内容可以访问云栖社区“异步社区”公众号查看。
再来一个很妖的问题
大咖讲Wireshark网络分析
有读者问,“叔叔,你那些很“妖”的网络问题是在哪找的?我也很感兴趣,但是从来没有遇到过。” 叔叔听完这句话,顿时觉得心里好苦——都是这些“妖怪”自己找上门的,我想躲都来不及,哪会主动去找啊!我们全球有几千用户,假如每位用户每年遇到一次网络故障,我就有看不完的包了。《Wireshark网络分析的艺术》中讲到的那些案例,其实只占极小部分,公司电脑里还躺着几百个案例等着整理呢。既然你们对“妖怪”问题有兴趣,我就再分享一个吧。
上个月有个项目组找到我,说他们的客户端往服务器写数据很慢,但是从服务器读数据却一直很快。该环境中的客户端、服务器和交换机分别属于不同厂商,三家人已经合作排查了一个月,实在没有头绪了,就来找我看看。作为一个叔叔,我要插播一点人生的经验——凡是影响严重,大老板非常关注,却迟迟未能解决的难题,你要尽一切可能承担下来。要知道你平时处理一百件小事都不会有人注意,个人技术也不会提高多少,但是解决掉一个大问题就足以令人刮目相看,自己的技术也会大有长进。
项目组其实已经做了不少有价值的测试,总结如下:
1.如果跳过交换机,直接把客户端和服务器用网线连起来,问题仍然存在。这说明交换机可以排除掉,只需专注客户端和服务器。
2.该客户端写数据到其他类型的设备时速度很快,似乎说明了客户端是好的。
3.其他类型的设备写数据到该服务器上时速度也很快,似乎说明了服务器也是好的。
综上所述,这一对客户端和服务器就像命理相克似的,平时双方都是工作正常的,但是碰到一起就出问题。我的老读者们可能已经想到了一些可能性:是不是有Delayed Ack和Nagle算法的冲突啊?还是网口的Speed/Duplex不一致啊?可惜都不是,项目组早就查遍了。事到如今,只好抓个网络包来分析了。
这个项目团队实在给力,在两边分别抓了几十个 tcpdump文件,合起来有 100 GB。解压缩的时候我的内心是崩溃的,他们说之所以抓了这么多,是因为该症状只是偶尔出现,所以不得不抓了很长时间。要想在 100 GB的网络包里找到偶尔发作的性能问题,对眼睛和耐心都是挑战,幸好Wireshark有个功能在此时可以派上用场:
打开从客户端抓到的包,单击菜单Statistics→TCP StreamGraph→Time-Sequence Graph (Stevens),就可以生成一个传输状态图了(不要问我“Stevens”是什么意思,我猜这是Wireshark在向伟大的技术作家Richard Stevens致敬)。
在理想状况下,随着数据的匀速传输, Sequence应该逐渐增加,从而形成一根较直的斜线。这次抓到的大多数tcpdump的确都是这样的,如图1所示。
![9f96ab39a252d1443c9388cbb2e6513c5b54e102](https://yqfile.alicdn.com/9f96ab39a252d1443c9388cbb2e6513c5b54e102.png?x-oss-process=image/resize,w_1400/format,webp)
Note:中间有一小段空白,表示那段时间的网络包没有抓到,这种现象可以忽视。
检查了几个tcpdump后,我终于遇到一个异常的,请看图2。大约从 3.0 秒到 11.0 秒之间,Sequence几乎没有增加,这说明当时传输几乎停滞了。我们只要找到当时发生了什么,就能解决问题。
![671726e844436a9a02b29f0f288745dd985bdece](https://yqfile.alicdn.com/671726e844436a9a02b29f0f288745dd985bdece.png?x-oss-process=image/resize,w_1400/format,webp)
在图2中单击Sequence曲线的 3.0 秒处,就能定位到相应的包号了。请看图3,定位到了No.264536上。
![022190cf81e78d91c074d4b5b10a8ae786c635ad](https://yqfile.alicdn.com/022190cf81e78d91c074d4b5b10a8ae786c635ad.png?x-oss-process=image/resize,w_1400/format,webp)
仔细来分析一下图3,服务器在No.264535中声明它的TCP接收窗口有 3145728 字节,可是客户端在No.264536中只发了9657个字节就停下来了,这说明它的拥塞窗口只有 9657 个字节,是客户端的拥塞窗口限制了传输速度,而不是服务器的接收窗口。接下来的包也类似,服务器在No.264537中声明了接收窗口仍然是 3145728 字节,然后客户端在No.264538中发出了 9874 个字节,只比上一次增加了 9874 - 9657 = 217 字节。如此反复,后面的包你们自己看吧。
注意:
本环境中客户端和服务器的MSS都是1460,我们之所以能看到大于 1460 字节的包,是因为客户端启用了LSO(Large Segment Offload)。
可见当时之所以传得这么慢,是因为客户端的拥塞窗口一直很小,而且增长得特别慢。学过TCP协议的读者应该都知道,在慢启动阶段虽然拥塞窗口很小,但是增长得很快,呈翻倍式增长。比如上一次发了 9657 字节,那下一次就应该是 9657 × 2 = 19314 字节。而拥塞避免阶段的拥塞窗口一般已经比较大,而且是以MSS(在此连接中为 1460 字节)增长的,再怎么样也不会只增长 217 个字节。图4表示了拥塞窗口在不同阶段的增长方式(横轴的单位是往返时间,纵轴的单位是MSS):
![abfc57d0fb09e91d55817077866007e3d24fd172](https://yqfile.alicdn.com/abfc57d0fb09e91d55817077866007e3d24fd172.png?x-oss-process=image/resize,w_1400/format,webp)
那么这问题就应该由客户端来负责了,他们的TCP算法肯定有问题。在“罪证”面前,客户端的厂商也承认了,不过他们也说了,协议栈的问题研究起来没那么快,说不定要几个月。而且为什么这台客户端写到别的服务器就没有问题呢?会不会是服务器上的其他问题触发的?
好人做到底。我觉得他们说的也有点道理,那就继续研究吧。仔细看图3,每当客户端收到服务器的确认包,就立即把数据发出去,比如No.264535和No.264536之间的时间差几乎可以忽略。而客户端发出数据之后,要等很久才能收到服务器的确认,比如No.264536和No.264537之间的时间差竟然有40毫秒。这个延迟在局域网中算是很可观的,为什么会这样呢?我也看不出来。
目前为止,我们分析的网络包都是在客户端抓的,接下来就去分析一下服务器上抓的tcpdump吧。可惜的是项目组在服务器上抓包时,并没有重现出性能问题,也就是说我手头这些包都是好的,只能死马当活马医,尽可能看看了。请看图5,在正常情况下,服务器的响应速度很快,客户端的拥塞窗口增长得也很快(4344724010136),包与包之间几乎都没有时间差。
![9e175fa904adcb9328b57d991c514743b2029b04](https://yqfile.alicdn.com/9e175fa904adcb9328b57d991c514743b2029b04.png?x-oss-process=image/resize,w_1400/format,webp)
这图是否能给我们一些启示呢?还真的能。如前面所说,客户端和服务器的MSS都是 1460 字节,我有TCP三次握手过程为证(见图6)。
![72383f99a9f73eb13529d4df36676f111eba511e](https://yqfile.alicdn.com/72383f99a9f73eb13529d4df36676f111eba511e.png?x-oss-process=image/resize,w_1400/format,webp)
这说明一个数据包从客户端的网卡发出来时,TCP层最多携带 1460 字节的数据。而从图5却可以看到,服务器收到包的时候,居然是 4344 字节、7240 字节、10136 字节……这是怎么回事呢?我在上本书里只介绍过LSO的工作原理,但没有介绍过LRO(large receive offload)。它是网卡上的一个功能,可以帮助服务器接收偏小的TCP包,缓冲处理成偏大的TCP包再交给服务器。也就是说,客户端收到的那些确认包,其实是服务器网卡缓冲处理了数据包之后才发出的。会不会是网卡缓冲处理时出了bug,导致多花了40毫秒呢?我们只需要在网卡上关闭LRO就知分晓了。我跟项目组打了个电话,让他们执行以下命令来关闭:
ethtool -K eth3 lro off
果然从此之后,他们就再也无法重现那个性能问题了,可以喝啤酒吃炸鸡庆祝了。看到这里你也许会问,客户端的协议栈算法问题不是还没有解决吗?为什么症状就消失了呢?这个问题的精妙之处就在此处:图3中服务器的响应很慢,客户端的拥塞窗口增加得也很慢;而图5中服务器的响应很快,客户端的拥塞窗口增加得也很快。这是否说明客户端其实采用了一种特殊的TCP算法,使拥塞窗口和响应时间挂钩呢?直到问题解决之后,我才意识到这一点。其实我在《Wireshark网络分析就这么简单》中介绍的TCP Vegas就是类似的,摘录如下:
“……Vegas则引入了一个全新的理念。本书之前介绍过的所有算法,都是在丢包后才调节拥塞窗口的。Vegas却独辟蹊径,通过监控网络状态来调整发包速度,从而实现真正的'拥塞避免'。它的理论依据也并不复杂:当网络状况良好时,数据包的RTT(往返时间)比较稳定,这时候就可以增大拥塞窗口;当网络开始繁忙时,数据包开始排队,RTT就会变大,这时候就需要减小拥塞窗口了。该设计的最大优势在于,在拥塞真正发生之前,发送方已经能通过RTT预测到,并且通过减缓发送速度来避免丢包的发生。
与别的算法相比,Vegas就像一位敏感、稳重、谦让的君子。我们可以想象当环境中所有发送方都使用Vegas时,总体传输情况是更稳定、更高效的,因为几乎没有丢包会发生。而当环境中存在Vegas和其他算法时,使用Vegas的发送方可能是性能最差的,因为它最早探测到网络繁忙,然后主动降低了自己的传输速度。这一让步可能就释放了网络的压力,从而避免其他发送方遭遇丢包。这个情况有点像开车,如果路上每位司机的车品都很好,谦让守规矩,则整体交通状况良好;而如果一位车品很好的司机跟一群车品很差的司机一起开车,则可能被频繁加塞,最后成了开得最慢的一个。”
在本案例中,由于服务器上LRO导致的 40 毫秒延迟,使得客户端以为网络上存在拥塞,所以就放慢了速度。而在LRO工作正常的环境中,自然不会有问题。前面说它们命理相克,正是出于这个原因。
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。