在网络性能问题排查思路那一节里,我提到了查看系统网络瓶颈的方法以及排查丢包问题的手段。
但就此分析网络问题还不够精细,有时网络资源并没有达到瓶颈,或者并没有丢包产生,但是网络传输速率就是很慢,或者有丢包产生,但无法知道丢包的详细过程,无法知道整个tcp传输过程的具体情况。
如何更加精细的查看网络包传输过程,答案就是抓包。
这一节我将用上传文件的抓包文件举例,用wireshark来分析tcp的传输过程以及文件传输速率慢的问题。
传输过程简单的可概括为,三次握手,慢启动,拥塞避免,快速恢复,四次挥手。三次握手,四次挥手的过程一般我们都比较熟,这里我不会特别来讲,着重来看下其他几个阶段。
其他几个阶段涉及到tcp里的窗口概念,我们首先来分下。
tcp的发送和接收数据是一个滑动窗口的模型,在tcp协议里,发送数据的滑动窗口叫发送窗口,接收数据的滑动窗口叫接收窗口。
发送窗口大小受制于对端接收窗口的大小和拥塞窗口的大小。
发送窗口大小 = min(对端的接收窗口,拥塞窗口)
tcp发送窗口受对端接收窗口的大小能动态调整,这种情况在局域网里面是可行的,但是在广域网里,数据传输过程中很有可能经过很多路由器或者交换机,如果中途设备网络处理能力变差,即使对端接收窗口能力大依然会造成严重的丢包,此时发送端即不应该继续发送数据包。
所以,拥塞窗口产生了,它有着能够动态感知网络拥塞情况来调整发送窗口的功能。
当发送重传的时候即认为此时网络产生了拥塞的情况。重传又分为快速重传和超时重传,拥塞窗口的大小会根据这两种重传方式做出不同的策略
超时重传
数据包在发送数据到对端时,会收到一个ack标志的数据包回来,当在一定时间内,发送端没有收到对端的ack包,那么发送端就会认为数据包丢掉了,会重新传递相同数据包到对端。这样的重传叫做超时重传。
快速重传
每次都等到超时再重传,会增大端与端的时延, 所以快速重传认为如果收到对端三次重复的ack,那么即认为包丢失了,然后将丢失的包马上传递到对端,不用等超时定时器触发。
对端重复ack是如何产生的?
tcp发包是一组一组的发往对端,如果一组当中某个包丢失了,对端接收到的是丢失后的包,那么回应的ack将会是丢包前最大的ack序号。
连接建立后,每次收到一个ack,那么拥塞窗口能发送的最大MSS(一个tcp包最大发送的字节数)就会翻倍。当最大发送数据到达ssthresh,就会进入拥塞避免阶段。
每过一个RTT(往返延时) ,拥塞窗口就会新增一个MSS大小。当碰到拥塞时,传输又会进入慢启动或者快速恢复阶段。那么如何衡量拥塞,当发生重传时即认为发送了拥塞。
快速恢复是为了避免每次碰到拥塞时,就进入慢启动阶段,让传输效率极剧降低这种情况出现。所以快速恢复采用遇到快速重传时,让拥塞窗口减半,然后MSS增长方式采用和拥塞避免阶段一样的低斜率线性增长的方式。然后当遇到超时重传时,整个传输过程依然会进入慢启动阶段。
慢启动阶段的特点,单位时间内,发包的数量在呈现指数级的增长。wireshark 可以通过tcp 时序图 Stevens来看到这种增长的变化。x轴是时间,y轴是包的序列号。
为什么慢启动之后会有比之前seq num 还低的点出现
因为发生了重传,发送的是之前发过的数据包。可以看到这第一波慢启动之后,又出现了好几次斜率比较陡的曲线,说明整个传输过程又经历了几次慢启动的过程,经过抓包,在5s到7.5s的这段期间,发生了大量的超时重传。
io graph 查看整个过程的传输速率。
以100ms的间隔去看整个过程传输速率变化,可以看到在拥塞发生之前,传输速率呈指数级别的上涨,在好几次超时重传后,传输速率直至降低到0以后又开始了指数级别的上涨,虽然斜率没有第一次那么急速,但依然是指数级别,所以可以认为实际上是在经历慢启动阶段。
在第二个波峰之后,又经过了一次传输速率的下降,没有第一次下降那么陡峭,然后速率呈现固定斜率的上涨趋势,这和快速恢复阶段的特定极其相似,可能整个tcp传输就是在进行快速恢复阶段。
传输速率除了拥塞带来的影响,还有接收窗口大小也会影响,接收窗口太小,传输速率也会提升不上去。
那么如何肯定这里传输速率的下降不是接收窗口的影响呢?
第一,tcp 时序图 Stevens找到对应速率下降的时间点附近在发生大量的超时重传,说明有大量丢包,进而说明网络状况不好。
第二,可以通过wireshark window scaling 去看接收窗口随时间的变化情况。
绿色代表接收窗口的大小,蓝色的点叫 bytes out 也就是在途数据(指已经发送但是还未被确认的数据。在途数据越多,说明发送端发送的数据就越多,整个过程,接收窗口在达到4M的时候就基本不变了。
发送端发送数据只是在差不多5s的时候达到了接收窗口的瓶颈,但由于网络拥塞,发送端发现之前发的包丢了,所以没有继续发送新的数据。而旧的数据包在慢慢ack过程中,所以bytes out变小了。
并且后续,发送的数据量大小都远远小于接收窗口的大小。如果接收窗口是瓶颈,在5s后,整个图应该是 bytes out和接收窗口处于一条基本重合的水平直线上,这里显然不是这样。
接收窗口大小受什么影响?
TCP接收窗口的大小在Linux系统中取决于TCP receive buffer的大小,而TCP receive buffer的大小默认由内核根据系统可用内存的情况和内核参数net.ipv4.tcp_rmem动态调节。
同时,不是TCP receive buffer的大小就等于TCP接收窗口的大小。有bytes/2^tcp_adv_win_scale的大小分配给应用。如果net.ipv4.tcp_adv_win_scale的大小为2,表示有1/4的TCP buffer给应用,TCP把其余的3/4给TCP接窗口。
在tcp报文里,留给窗口的表达式只有16位,这样导致,tcp的窗口最大只能表示64kb,所以在tcp窗口大于64kb时 需要利用TCP Options的Window scale字段。在系统内核参数设置里,对应的就是net.ipv4.tcp_window_scaling参数,这个参数会将window字段乘以2的scale次方作为实际窗口大小。
这个字段在握手的时候会告诉对方。
所以如果接收窗口是瓶颈,那么可以调大接收方的receive buffer 以及tcp_window_scaling参数。
这一节 主要用了 tcp stream graphs 宏观的去分析了文件上传时tcp的传输过程,wireshark提供的高级功能,这一节只是冰山一角,希望能抛砖引玉。
我认为,网络抓包无处不在,其实随手就可以抓取上网浏览的包去进行分析,然后通过wireshark把包传输过程的表现都用理论知识找到对应的解释,不断深挖下去,便会融会贯通。