在现代软件开发中,服务的性能瓶颈是一项常见的挑战。当我们遇到性能瓶颈时,通常会使用一些常规的工具和技术来分析问题,例如火焰图、耗时日志和trace等。虽然这些工具可以帮助我们了解服务的运行情况和性能瓶颈,并且往往非常有用,但是对于极其庞大的项目来说,这些工具也会让我们掉入细节的陷阱中。因此,本文将介绍如何利用tcpdump这一网络抓包工具,来帮助您另辟蹊径地分析服务的性能瓶颈。
这里我们首先以一个实际问题作为引子,讲述如何利用tcpdump快速找出性能瓶颈的分析过程。
服务A是一个分布式的数据流加工服务,能够动态地针对不同类型的业务进行加工。然而,在处理同等流量的不同业务时,A服务的加工速度存在较大波动,有时较快,有时较慢。即使是同一类型的业务,在不同时间点,加工速度也存在差异。此外,数据流向服务B自身逻辑也较为复杂,其监控图上也偶见长尾毛刺。由于双方都是分布式服务,交互的数据量也相当巨大,查证问题的成本比较高。
针对上面的场景,可能发生的原因比较多。开发人员曾经怀疑是内网交换机的网络问题、服务B所在部分节点的机器问题、服务A所在部分节点的机器问题等等。
这里我们整理一下,导致此类问题发生的可能因素:
- 发送方处理延迟:发送方在两次数据发送之间,存在复杂的计算和处理逻辑。那我们需要检查一下:
- 是否存在串行的阻塞逻辑,io等待、锁等资源等待。可以看看压力大的时候,CPU的si还非常高,再上perf之类工具分析;
- 如果是cpu型的任务,cpu是否跑满。如果程序跑在容器中,cpu即使没跑慢,也要检测是否受到其他容器的影响;
- 如果服务是用gc型语言开发,是否存在gc暂停。
- 接受方处理延迟:检查逻辑同上。
- TCP拥塞控制:TCP拥塞控制是为了避免网络拥塞而设计的一种机制,通过动态调整发送方的拥塞窗口大小来实现。然而,在某些情况下,TCP拥塞控制可能导致性能上不去的问题。其可能的原因可能和中间的交换机丢包、带宽不足、长距离网络延迟、窗口限制、应用层处理延迟等有关系。
回到上面的问题,首先我们使用tcpdump进行抓包。
sudo tcpdump -i any tcp and port xxx -w dump.pcap
Wireshark 可以直接打开文件 dump.pcap。
Wireshark获取到获取收到的包后,我们可以大致浏览一下,在Info这一列是否存在这些情况:
- 丢包
Info那一列存在比较多的TCP Fast Retransmission(3个以上的TCP Dup ACK)或者是TCP Retransmission(超时重传)
- 接收方的性能跟不上
Info那一列存在比较多的TCP zerowindow
如下图:
假设接收方处理数据的速度跟不上接收数据的速度,缓存就会被占满,从而导致接收窗口为 0,当发送方接收到零窗口通知时,就会停止发送数据。
与之相对的是TCP fullwindow,TCP fullwindow代表发送方的拥塞窗口达到最大值,如果发送方"在途字节数"超过接收方声明的数据包大小,发送方的缓冲区满了,此时发送方会暂停发送数据,直到收到接收方的ACK或触发超时重传。这里可能是接收方处理速率慢,也可能是当前的带宽延迟下,网络通道跑满了。
这里说一个例外的情况,由于抓包不完整,wireshark可能会把tcp 心跳包和心跳包的回复分别解释为TCP Retransmission或TCP Dup ACK。这样每一个包的序号下面都会出现TCP Retransmission。在确认重传没有大的问题的情况下,可以忽略掉这些包。
第一次浏览后,我们抓取的包并未出现上面的丢包或窗口问题。
现在我们开始第二锤: 流分析。
流分析的第一步,我们可以选出耗时间隔比较的流,如使用下面的过滤:
tcp.time_delta > 0.3
这里的0.3代表0.3秒,这样可以将阻塞等待过长的流提取出来,作为下一步备选的流。
这里我们选定一个包,然后点击Statistics->TCP StreamGraph->TCP Sequence Graph( Stevens)菜单。
从上图中,我们可以看到前面几个包之后,发送方暂停的时间比较长,然后有一段很陡峭的数据传输。整体的时间是1.09秒,其中中间暂停的时间大概825ms,数据集中传输大概190ms。
分别用鼠标点击数据包,会定位到具体发送的数据包。
此时,我们怀疑两次请求之间的业务逻辑处理耗时过长,导致发送端发送暂停。对区间的代码片段进行测试之后,果然发现性能瓶颈所在。
此外,在数据传输过程中,从0.90s附近,我们也发现两处发送暂停。这里截取其中的一处:
79 0.899717 client server TCP 16844 [ACK] Seq=48391 Ack=1701 Win=32512 Len=16776 TSval=3859531543 TSecr=3899600801 [TCP segment of a reassembled PDU]
81 0.900077 server client TCP 68 [ACK] Seq=1701 Ack=48391 Win=127360 Len=0 TSval=3899600801 TSecr=3859531543
83 0.900089 server client TCP 68 [ACK] Seq=1701 Ack=56779 Win=144128 Len=0 TSval=3899600801 TSecr=3859531543
85 0.900099 server client TCP 68 [ACK] Seq=1701 Ack=65167 Win=160896 Len=0 TSval=3899600801 TSecr=3859531543
87 0.965175 client server TCP 11252 [ACK] Seq=65167 Ack=1701 Win=32512 Len=11184 TSval=3859531609 TSecr=3899600801 [TCP segment of a reassembled PDU]
89 0.965267 client server TCP 5660 [ACK] Seq=76351 Ack=1701 Win=32512 Len=5592 TSval=3859531609 TSecr=3899600801 [TCP segment of a reassembled PDU]
从上图可以看出,发送窗口大小没有变小,也没有重复ACK,服务端的ACK也很快就回复了,所以基本可以排除拥塞控制导致的报文延迟的可能性。
再结合RTT图看一下:
基本上内网传输,所有包的RT时间非常短。
这里应该是发送方(客户端)内部的原因,比如处理任务切换,线程调度等导致的。
至此,通过Stevens序列图,我们很快分析得到性能无法提升的原因。
此外,丢包或窗口受限,我们也可以通过Stevens序列图快速得到。
一般丢包或乱序发生时,后面的包序号要比前面的大,如下图:
通过TCPDump,我们可以轻易分析出性能无法提升的原因。
其实不仅如此,在一些其他场景,也能大方异彩。比如,成本节省中,我们会对https包的流量进行分析,长连接相比短链接可以大大节省流量。通过tcpdump的IOGraph,我们轻松可以对过滤的包进行流量分析,很容易看出长短连接的流量趋势图。