一定有过在网页或者手机上下载照片的体验,如果数据传输太慢,那你的体验一定十分糟糕。你看,互联网实体之间的数据快速传输对用户体验至关重要。这里涉及到的其实就是网络传输问题。所以,今天我们就通过生产实践中的案例,来探讨一下互联网服务中的数据传输性能。

说到底,网络传输问题其实就分两种:

  1. 数据根本没有传递;
  2. 数据传送速度较慢。

“数据没有传递”虽然看起来更严重,但是相对“数据传送缓慢”来说,更容易判断和解决。所以,这一讲,我们就重点解决第二种问题。我们一起来看看,为什么网络传送速度会慢,在众多原因中怎么快速诊断出关键问题来,又该如何去解决。

造成网络传输缓慢的原因很多,我们这一讲,就是帮助你快速诊断问题出在哪里:是客户端,是服务器端,还是网络本身?在此基础上,你才能专门针对具体的领域继续分析。

为什么数据传输慢?

我们先看一下,都有哪些可能的原因会导致数据传输缓慢呢?在宏观上,这种问题的可能原因可以分为三种场景:

  1. 客户端应用程序的原因;
  2. 网络的原因;
  3. 服务器应用程序的原因。

也就是说,可能是由于数据发送方过载,而没有向接收方发送数据;也可能是网络通道很慢;又或者是数据接收方的服务器太忙,从而无法从网络缓冲区读取数据。

为了描述方便,我们根据平时客户浏览网页的场景,假设客户端是数据接收方,而服务器端是数据发送方。

进行此类分析诊断时,负责的工程师通常需要快速隔离出上述不同场景,以便他们可以专注于特定场景里面的可疑组件,并对本质原因进行更深入的分析。但这一快速诊断过程会遇到很多难点。

首先,数据传输涉及多个网络实体,包括两台机器(也就是发送者和接收者)和网络路由,这与仅涉及一台机器的常见性能问题形成鲜明对比。

其次,这种诊断涉及多层信息,包括应用程序层和网络传输层。为了找出原因,工程师必须检查各种数据,包括客户端日志、服务器日志、网络统计信息、CPU使用情况等。这些检查需要花费很多时间和精力,并且通常需要性能工程师的经验和专业知识。

更加让人郁闷的是,这些日志往往分散在不同的地方,比如客户端和服务器。为了节省时间和精力,性能工程师迫切需要更智能的工具,以帮助他们快速找出根本原因。

所以今天,我专注于解决这样的一个问题,就是:快速确定应归咎的组件范围和场景(无论是发送方、接收方还是网络本身)。我提出了一种当发生数据传输缓慢的问题时,可以自动隔离原因的解决方案。毕竟,你只有找出了要对“数据传输慢”负责任的那一部分,才可以进行后续分析工作,最终确定真正的问题。

如何判断问题所在位置?

要想快速诊断,我们需要先看看三种问题场景的不同特征。

这个解决方案本质上依靠的是客户端和服务器端的TCP层面的特征。TCP是传输层协议之一,可提供有序且可靠的流字节传输,是当今使用最广泛的传输协议。TCP具有流控制功能,可避免接收方过载。接收方设置专用的接收缓冲区,发送方设置相应的发送缓冲区。数据发送方(服务器)的发送缓冲区和数据接收方(客户端)的接收缓冲区,都可以通过操作系统来监测当前队列大小。

为了能够识别瓶颈,你需要在发送方和接收方的传输层上,收集有关队列大小的信息。有很多收集此类信息的方法。你有两种工具可以使用,分别是Netstat和ss。

Netstat是一个命令行工具,可以显示网络连接和网络协议统计信息。我们主要是用它来观察TCP / IP套接字的发送队列和接收队列的大小。而ss命令,可以显示套接字统计信息,包括显示TCP以及其他类型套接字的统计信息。类似于Netstat,ss还可以显示发送和接收队列大小。

除了相应的工具的介绍,为了帮助你理解,我们还需要先重温一下传输数据时候,应用层和TCP层的交互。

上图显示了任何基于TCP的数据传输中的典型流程。关于系统调用和网络传输的五个步骤如下:

  1. 在步骤A,服务器应用程序发出write()系统调用,并将应用程序数据复制到套接字发送缓冲区。
  2. 在步骤B,服务器的TCP层发出send()调用,并将一些数据发送到网络;数据量受TCP的拥塞控制和流控制。
  3. 在步骤C,网络将数据逐跳路由到接收方(IP路由协议在这部分中发挥作用)。
  4. 在步骤D,客户端的TCP层将通过recv()系统调用接收数据,数据放入接收缓冲区。
  5. 在步骤E,客户端应用程序发出read()调用,以接收数据并将其复制到用户空间。

接下来,我们就来看看三种不同场景下的问题特征是什么样的。

我们先看第一个场景,客户端接收数据缓慢的情况。为了重现这一场景,我们做一个实验,让发送端发送一段固定大小的数据给接收方。我们强制接收方,也就是客户端,减慢数据的接收速度。具体做法,就是在应用程序代码的read()调用之前,注入了一定的延迟,这种场景代表了客户端数据接收成为瓶颈的情况。

上图显示的是数据发送方的发送缓冲区,SendQ(Send Queue)的大小变化。开始时候,数据发送调用send(),立刻注满SendQ。随着数据的传输,慢慢变为0。

第二张图是客户端的接收缓冲区,RecvQ(Receive Queue)的大小变化,客户端因为应用程序运行缓慢,所以RecvQ具有一定的积累,这可以由非零值来看出。这些非零值持续了一段时间,随着应用程序不断地读取,最终RecvQ减为0。

对于第二种场景,也就是数据发送方是瓶颈的情况,我们强制发送方(即服务器端),放慢数据的发送速度。具体来说,我们在应用程序代码中,对write()的调用之前注入了一定的延迟,模拟了发送者是瓶颈的情况。

上图显示了服务器端的SendQ的值,你可以看到,SendQ几乎全部是零。这是因为发送端是瓶颈,其他地方不是瓶颈,所以任何SendQ的数据会被很快发送出去。

你可以在图片中看到一个持续时间很短的峰值,这是因为SendQ取样的时候恰好取到数据还没有被传输到网络中的时候。但因为这个峰值持续时间很短,简单的过滤就可以去掉。

接收端的RecvQ显示在下图,你可以看到,因为接收端不是瓶颈,RecvQ是零。

第三个场景,是网络本身是瓶颈造成的数据传输缓慢。我们通过向网络路径注入延迟来创造这一场景,以使TCP仅能以非常低的吞吐量进行传输。

图片中显示了发送端的SendQ值,你可以看到它的值不为零,因为那些数据不能很快地被传送出去。

再来看接收端的RecvQ,如下图。RecvQ全为零,这些零值就代表了快速的数据传递。

通过上面三种场景的分析,尤其是对发送端SendQ和接收端RecvQ的观察,我们不难总结出规律来。正常的数据传输情况下,客户端的接收队列和服务器端的发送队列都应该是零。

反之,如果数据传输缓慢,则有如下几种情况:

  1. 如果客户端上的接收队列RecvQ不为零,则客户端应用程序是性能瓶颈;
  2. 如果服务器上的发送队列SendQ为零,则服务器应用程序是性能瓶颈;
  3. 如果客户端的接收队列RecvQ为零,而服务器的发送队列SendQ为非零,则网络本身是性能瓶颈。

为了帮助你加深记忆,我用表格来做了个归纳。

你可以通过这个表格,快速判断问题出现的位置。

解决方案如何落地?

根据前面的分析和总结,我们现在提出解决方案。这是一个基于状态转移的方案,需要从客户端和服务器端收集几个关键点的信息。

为了帮助你理解,我们需要先来看看数据请求和传输流程图。就用常见的HTTP协议的Request和Response方式来描述,如下图所示。

当客户端需要下载服务器的数据时,首先在T0发出数据请求;网络将请求发送到服务器后,服务器在T1收到数据。

然后,服务器开始准备数据,数据准备好后,服务器将开始在T2时发回数据。通过一系列write()调用。发送在T4完成。网络传输后,客户端在T3开始接收数据,并在T5完成接收。请注意,尽管其他时间戳是按照严格的顺序,T3和T4的顺序可能会因实际情况而异。具体来说,对于小数据传输时,T4可以先于T3,因为单个write()调用就足够了。对于大数据传输,通常使用T3在T4之前。

接下来,我们来看看基于状态机的解决方案,它是一个针对HTTP数据传输问题的,完整而具体的解决方案。

从上面的过程中,我们可以看到,如果服务器无法接收到数据请求,则数据传递将不会发生,因此不会完成。

我用下图来表示整个状态机。这个状态机展示了整个HTTP数据传输的过程,包括Request和Response。如果数据传递成功,状态机最后会到达状态F。

如上图,数据传递的初始状态为State-S。客户端发出请求后,它将移至状态A;当网络通道完成其工作,并将请求传递到服务器OS时,状态变为B。当服务器准备好数据,并开始发出数据的第一个字节时,状态变为C。

当客户端收到第一个字节后,状态变为D;最后当服务器发出最后一个字节时,状态变为E。或者这两个次序交换,成功进行数据传递的最终状态是State-F。

在整个过程中,如果发生其他的转移,那么就是网络传输有问题了。我们就可以根据发送端的发送队列和接收端的接收队列长度的变化,轻松判断是谁的问题,比如是客户端,服务器或是网路。

总结

今天我们讲述了,互联网服务在传输数据时,如果发生传输速度太慢的问题,怎样才能快速地诊断到底是客户端、服务器端,还是网络的问题。

唐代诗人高适的《燕歌行》有几句诗:“山川萧条极边土,胡骑凭陵杂风雨。战士军前半死生,美人帐下犹歌舞。”说的是前方的战士,在前线出生入死;后方却有人逍遥自在的观赏美人歌舞,醉生梦死。这种冰火两重天的讽刺,部分原因,就是责任没有分清,以至于滥竽充数者可以逍遥自在。

对待数据传输缓慢问题,我们也很希望能快速地搞清责任,分清是哪里的问题,然后才能有针对性地继续分析。我们的解决方案就是根据TCP的Send和Receive队列大小变化,来快速诊断的方案。它能智能而快速地分清问题的大致范围:就是数据发送方、数据接收方,还是网络。