导语 传输层最重要的是TCP和UDP协议,所以这儿整理的主要是对这两种协议的原理和常见故障分析。正文1、基本原理 TCP提供了面向连接的可靠传输服务。要优化TCP,我们首先要掌握TCP协议的基本原理,比如流量控制、慢启动、拥塞避免、延迟确认以及状态流图(如下图所示)等。掌握这些原理后,就可以在不破坏TCP正常工作的基础上,对它进行优化。 UDP不提供复杂的控制机制,利用IP提供面向无连接的通信服务。 连接建立与断开 TCP状态转移 TCP流量控制 TCP半连接与全连接 TCP半连接队列和全连接队列满了会发生什么?又该如何应对? 在TCP三次握手的时候,Linux内核会维护两个队列,分别是: 半连接队列,也称SYN队列; 全连接队列,也称accepet队列; 服务端收到客户端发起的SYN请求后,内核会把该连接存储到半连接队列,并向客户端响应SYNACK,接着客户端会返回ACK,服务端收到第三次握手的ACK后,内核会把连接从半连接队列移除,然后创建新的完全的连接,并将其添加到accept队列,等待进程调用accept函数时把连接取出来。 不管是半连接队列还是全连接队列,都有最大长度限制,超过限制时,内核会直接丢弃,或返回RST包。 在LISTEN状态时,RecvQSendQ表示的含义如下: RecvQ:当前全连接队列的大小,也就是当前已完成三次握手并等待服务端accept()的TCP连接个数;TCP全连接队列最大值取决于somaxconn和backlog之间的最小值,也就是min(somaxconn,backlog)。somaxconn是Linux内核的参数,默认值是128,可以通过procsysnetcoresomaxconn来设置其值;backlog是listen(intsockfd,intbacklog)函数中的backlog大小,Nginx默认值是511,可以通过修改配置文件设置其长度; SendQ:当前全连接最大队列长度,上面的输出结果说明监听8088端口的TCP服务进程,最大全连接长度为128; 在非LISTEN状态时,RecvQSendQ表示的含义如下: RecvQ:已收到但未被应用进程读取的字节数; SendQ:已发送但未收到确认的字节数; 查看当前半连接队列大小(半连接大小由内核参数somaxconn及tcpmaxsynbacklog决定)netstatnatpgrepSYNRECVwcl 查看当前是否存在全队列溢出的情况(间隔数秒后再次运行,看数值是否增多)netstatsgrepoverflowed 查看当前是否存在半连接队列溢出的情况(间隔数秒后再次运行,看数值是否增多) netstatsgrepSYNstoLISTEN2、网络性能测试 网络性能评估主要是监测网络带宽的使用率,将网络带宽利用最大化是保证网络性能的基础,但是由于网络设计不合理、网络存在安全漏洞等原因,都会导致网络带宽利用率低。要找到网络带宽利用率低的原因,可以对网络传输进行监控,此时就需要用到一些网络性能评估工具,而iperf就是这样一个网络带宽测试工具。 iperf和netperf都是最常用的网络性能测试工具,测试TCP和UDP的吞吐量。它们都以客户端和服务器通信的方式,测试一段时间内的平均吞吐量。 iperf是一个基于TCPIP和UDPIP的网络性能测试工具,它可以用来测量网络带宽和网络质量,还可以提供网络延迟抖动、数据包丢失率、最大传输单元等统计信息。网络管理员可以根据这些信息了解并判断网络性能问题,从而定位网络瓶颈,解决网络故障。本机带宽大小 本机带宽可以用ethtool来查询,它的单位通常是Gbs或者Mbs,不过注意这里小写字母b,表示比特而不是字节。我们通常提到的千兆网卡、万兆网卡等,单位也都是比特。如下,eth0网卡就是一个千兆网卡:ethtooleth0grepSpeedSpeed:1000Mbs centos系统iperf安装 方法一:yum方式 目前,iperf的最新版本为iperf3,可以运行下面的命令来安装:yuminstalliperf3 方法二:安装包方式 iperf支持Win32、Linux、FreeBSD、MacOSX、OpenBSD和Solaris等多种操作系统平台。可以从iperf官方网站:http:iperf。fr下载各种版本,这里下载的软件包为iperf3。0。tar。gz,安装过程如下:tarzxvfiperf3。0。tar。gzcdiperfmakemakeinstalliperf使用 服务器端专用选项的含义如下表所示 客户端专用选项的含义如下表所示 客户端与服务器端公用选项的含义如下表所示图片 TCP性能测试(吞吐) iperf测试TCP带宽的原理比较简单,在客户端和服务端建立三次握手连接后,客户端带宽的大小等于发送的总数据除以发送的总时间。对服务端测得的带宽,则是接收的总数据除以所花时间。 步骤一:在目标机器上启动iperf服务端: 步骤二:在另一台机器上运行iperf客户端,运行测试: 注意禁止使用同一台机器进行测试,否则结果不准。 步骤三:查看结果报告 客户端处的输出 可以看到每15秒传输的数据量在995MB左右,刚好与Bandwidth列的值对应起来,网卡的带宽速率维持在995Mbitssec左右,而测试的服务器是千兆网卡,这个测试值也基本合理。在输出的最后,iperf还给出了总的数据发送、接收量,以及带宽速率平均值,通过这些值,基本可以判断网络带宽是否正常,网络传输状态是否稳定。UDP性能测试(丢包和时延) iperf也可以用于测量UDP数据包的带宽、抖动和丢包率。但是,由于UDP协议是一个面向非连接的轻量级传输协议,不提供可靠的数据传输服务。因此,一般不对UDP进行带宽测试。 iperf测试UDP性能时,客户端可以指定UDP数据流的速率。客户端发送数据时,将根据客户端提供的速率计算数据包发送之间的时延。客户端还可以指定发送数据包的大小。每个发送的数据包都有一个ID号,服务器端根据该ID号来确定数据报丢失和乱序。 数据报传输延迟抖动(Jitter)的测试由服务器端完成,客户发送的报文数据包含有发送时间戳,服务器端根据该时间信息和接收到报文的时间戳来计算传输延迟抖动。传输延迟抖动反映传输过程中是否平滑。由于它是一个相对值,所以并不需要客户端和服务器端时间同步。 目标端的服务器命令不变,客户端命令只需再加一个u即可 主要看服务器端的结果,截图如下: 客户度结果截图如下: Jitter列表示抖动时间,或者称为传输延迟,LostTotal列表示丢失的数据包和总的数据包数量,后面的55是平均丢包的比率,Datagrams列显示的是总共传输数据包的数量。在这个输出中,详细记录了在传输过程中,每个阶段的传输延时和丢包率,在UDP应用中随着传输数据的增大,丢包率和延时也随之增加。对于延时和丢包可以通过改变应用程序来缓解或修复,例如视频流应用,通过缓存数据的方式可以容忍更大的延时。3、常见故障一:网络请求延迟增大 网络延迟即网络数据传输所用的时间。注意,这个时间可能是单向的,指从源地址发送到目的地址的单程时间;也可能是双向的,即从源地址发送到目的地址,然后又从目的地址发回响应,这个往返全程所用的时间。 另一个指标应用程序延迟,是指从应用程序接收到请求,再到发回响应,全程所用的时间。通常,应用程序延迟也指的是往返延迟,是网络数据传输时间加上数据处理时间的和。 网络延迟测试工具 最简单的,使用ping来测试网络延迟。 ping基于ICMP协议,它通过计算ICMP回显响应报文与ICMP回显请求报文的时间差,来获得往返延时。这个过程并不需要特殊认证,常被很多网络攻击利用,比如端口扫描工具nmap、组包工具hping3等等。所以,为了避免这些问题,很多网络服务会把ICMP禁止掉,这也就导致我们无法用ping,来测试网络服务的可用性和往返延时。 这时,我们可以用traceroute或hping3的TCP和UDP模式(必须指定端口),来获取网络延迟。比如,以baidu。com为例,可以执行下面的hping3命令,测试你的机器到百度搜索服务器的网络延迟: traceroute会在路由的每一跳发送三个包,并在收到响应后,输出往返延时。如果无响应或者响应超时(默认5s),就会输出一个星号。 分析示例 选取《Linux性能优化实践》中的一个例子。服务端为修改配置后的nginx服务器(服务端口号为8080),客户端为wrk命令行wrklatencyc100t2timeout2http:172。22。31。228:8080发起的性能测试。 服务端通过tcpdump进行抓包i指定网卡,一定要指定正确的网卡,否则抓不到任何包tcpdumpnntcpport8080ilownginx。pcap 对于多并发请求,我们可以选中关心的tcp报文中的一个,点击右键FollowTcpStream即可,等同于tcp。streameq1这样来过滤,如下图所示:tcp。stream可理解为握手后一对传输之间的通讯被赋予的一个号码。可以标识一次完整的tcp通讯,包括建立、通讯和释放。 过滤后,观察这一次完整的通信过程: 通过这个图就可以看出,前面三次握手,以及第一次HTTP请求和响应还是挺快的,但第二次HTTP请求就比较慢了,特别是客户端在收到服务器第一个分组后,40ms后才发出了ACK响应(图中红色框)。 再仔细观察Wireshark的界面,其中,1108号包,就是刚才说到的延迟ACK包;下一行的1110,则是Nginx发送的第二个分组包,它跟653号包组合起来,构成一个完整的HTTP响应(ACK号都是89)。 第二个分组没跟前一个分组(653号)一起发送,而是等到客户端对第一个分组的ACK后(1108号)才发送,这看起来跟延迟确认有点像,只不过,这儿不再是ACK,而是发送数据。TCP延迟确认 针对TCPACK的一种优化机制,即不用每次请求都发送一个ACK,而是先等一会儿(比如40ms),看看有没有顺风车。如果这段时间内,正好有其他包需要发送,那就捎带着ACK一起发送过去。当然,如果一直等不到其他包,那就超时后(40ms)单独发送ACK。 查询TCP文档(执行mantcp),只有TCP套接字专门设置了TCPQUICKACK,才会开启快速确认模式;否则,默认情况下,采用的就是延迟确认机制。 而通过stracef命令查看客户端的系统调用,并没有设置TCPQUICKACK,只设置了TCPNODELAY(表示禁用Nagle算法),这里说明客户端采用了延迟确认机制。stracefwrklatencyc100t2timeout2http:172。22。31。228:8080。。。setsockopt(52,SOLTCP,TCPNODELAY,〔1〕,4)0。。。Nagle算法(纳格算法) Nagle算法,是TCP协议中用于减少小包发送数量的一种优化算法,目的是为了提高实际带宽的利用率。 举个例子,当有效负载只有1字节时,再加上TCP头部和IP头部分别占用的20字节,整个网络包就是41字节,这样实际带宽的利用率只有2。4(141)。往大了说,如果整个网络带宽都被这种小包占满,那整个网络的有效利用率就太低了。 Nagle算法正是为了解决这个问题。它通过合并TCP小包,提高网络带宽的利用率。Nagle算法的基本定义是任意时刻,最多只能有一个未被确认的小段。所谓小段,指的是小于MSS尺寸的数据块,所谓未被确认,是指一个数据块发送出去后,没有收到对方发送的ACK确认该数据已收到。在收到这个分组的ACK前,不发送其他分组。这些小分组会被组合起来,并在收到ACK后,用同一个分组发送出去。 显然,Nagle算法本身的想法还是挺好的,但是碰上Linux默认的延迟确认机制后,就不是这么好了。因为它们一起使用时,网络延迟会明显。如下图所示: 当Sever发送了第一个分组后,由于Client开启了延迟确认,就需要等待40ms后才会回复ACK。同时,由于Server端开启了Nagle,而这时还没收到第一个分组的ACK,Server也会在这里一直等着。直到40ms超时后,Client才会回复ACK,然后,Server才会继续发送第二个分组。 因此将nginx服务器的配置禁止Nagle算法即可降低网络请求时延。 此时Nginx不用再等ACK,536和540两个分组是连续发送的;而客户端虽然仍开启了延迟确认,但这时收到了两个需要回复ACK的包,所以也不用等40ms,可以直接合并回复ACK。TCPNODELAY 启动TCPNODELAY,就意味着禁用了Nagle算法,允许小包的发送。对于延时敏感型,同时数据传输量比较小的应用,开启TCPNODELAY选项无疑是一个正确的选择。比如,对于SSH会话,用户在远程敲击键盘发出指令的速度相对于网络带宽能力来说,绝对不是在一个量级上的,所以数据传输非常少;而又要求用户的输入能够及时获得返回,有较低的延时。如果开启了Nagle算法,就很可能出现频繁的延时,导致用户体验极差。当然,你也可以选择在应用层进行buffer,比如使用java中的bufferedstream,尽可能地将大包写入到内核的写缓存进行发送;vectoredIO(writev接口)也是个不错的选择。 对于关闭TCPNODELAY,则是应用了Nagle算法。数据只有在写缓存中累积到一定量之后,才会被发送出去,这样明显提高了网络利用率(实际传输数据payload与协议头的比例大大提高)。但是这又不可避免地增加了延时;与TCPdelayedack这个特性结合,这个问题会更加显著,延时基本在40ms左右。当然这个问题只有在连续进行两次写操作的时候,才会暴露出来。 连续进行多次对小数据包的写操作,然后进行读操作,本身就不是一个好的网络编程模式,在应用层就应该进行优化。 对于既要求低延时,又有大量小数据传输,还同时想提高网络利用率的应用,大概只能用UDP自己在应用层来实现可靠性保证了。排查思路 在发现网络延迟增大后,可以用traceroute、hping3、tcpdump、Wireshark、strace等多种工具,来定位网络中的潜在问题。比如, 使用hping3以及wrk等工具,确认单次请求和并发请求情况的网络延迟是否正常。 使用traceroute,确认路由是否正确,并查看路由中每一跳网关的延迟。 使用tcpdump和Wireshark,确认网络包的收发是否正常。 使用strace等,观察应用程序对网络套接字的调用情况是否正常。 这样,你就可以依次从路由、网络包的收发、再到应用程序等,逐层排查,直到定位问题根源。4、常见故障二:DoS攻击定义 DoS(DenailofService),即拒绝服务攻击,指利用大量的合理请求,来占用过多的目标资源,从而使目标服务无法响应正常请求。 DDoS(DistributedDenialofService)则是在DoS的基础上,采用了分布式架构,利用多台主机同时攻击目标主机。这样,即使目标服务部署了网络防御设备,面对大量网络请求时,还是无力应对。 从攻击的原理上来看,DDoS可以分为下面几种类型。 第一种,耗尽带宽。无论是服务器还是路由器、交换机等网络设备,带宽都有固定的上限。带宽耗尽后,就会发生网络拥堵,从而无法传输其他正常的网络报文。 第二种,耗尽操作系统的资源。网络服务的正常运行,都需要一定的系统资源,像是CPU、内存等物理资源,以及连接表等软件资源。一旦资源耗尽,系统就不能处理其他正常的网络连接。 第三种,消耗应用程序的运行资源。应用程序的运行,通常还需要跟其他的资源或系统交互。如果应用程序一直忙于处理无效请求,也会导致正常请求的处理变慢,甚至得不到响应。 比如,构造大量不同的域名来攻击DNS服务器,就会导致DNS服务器不停执行迭代查询,并更新缓存。这会极大地消耗DNS服务器的资源,使DNS的响应变慢。 无论是哪一种类型的DDoS,危害都是巨大的。那么,如何可以发现系统遭受了DDoS攻击,又该如何应对这种攻击呢?DoS攻击模拟 此处将用到hping3工具,它可以用来构造TCPIP协议数据包,对系统进行安全审计、防火墙测试、DoS攻击测试等。 DoS攻击的问题是,当我们发送所有这些数据包的服务器,它有我们的地址在里面。所有的管理员要做的就是查看日志,我们的地址会很快的被查找出来,然后就GG了。我们不仅要发起SYN洪流,而且要欺骗我们的地址,攻击之前,让我们更深入地讨论SYN泛洪的概念。 SYN洪流会向服务器疯狂的发送请求数量,以便用尽所有的资源。你可能会好奇:SYN与使用资源有什么关系?。那么这就是TCP三方握手。SYN代表synchronize。当我们发送一个SYN数据包时,我们将建立一个连接。 我们可以看到攻击者向受害者发送了很多SYN数据包(带有欺骗地址)。受害者用SYNACK做出响应来确认连接,但由于没有响应,这样就会使得服务端有大量的处于SYNRECV状态的TCP连接,这种等待状态的TCP连接,通常也称为半开连接。由于连接表的大小有限,大量的半开连接就会导致连接表迅速占满,从而无法建立新的TCP连接。 使用tcpdump命令抓包tcpdumpilontcpport8080 使用hping3进行syn泛洪攻击p端口号iu100表示每隔100微秒发送一个网络帧S发送TCP的SYN包A发送TCP的ACK包c参数可以指定收发数据包的个数a伪造源IPflood尽可能快的发送,慎用hping3Sp8080iu100172。22。31。228a1。2。3。4 放入wireshark查看抓包情况: Flags〔S〕表示这是一个SYN包。大量的SYN包表明,这是一个SYNFlood攻击。 查看当前TCP半连接队列大小: SYNRECVED状态的连接达到最大值256,且持续不变。之前一直误以为半连接队列的大小只由tcpmaxsynbacklog参数决定,而查看本机该参数的大小并不为256。其具体决定因素可查看文章TCP半连接队列和全连接队列满了会发生什么?又该如何应对?catprocsysnetipv4tcpmaxsynbacklog解决方案 为了缓解SYNFLOOD等,利用TCP协议特点进行攻击而引发的性能问题,可以考虑优化与SYN状态相关的内核选项,比如采取下面几种措施。 这里给出几种防御SYN攻击的方法: 方法一:增大半连接队列; 要想增大半连接队列,我们得知不能只单纯增大tcpmaxsynbacklog的值,还需一同增大somaxconn和backlog,也就是增大全连接队列。否则,只单纯增大tcpmaxsynbacklog是无效的。 增大tcpmaxsynbacklog和somaxconn的方法是修改Linux内核参数:echo1024procsysnetipv4tcpmaxsynbacklogecho1024procsysnetcoresomaxconn 增大backlog的方式,每个Web服务都不同。该参数即为listen()的第二个参数。 方法二:开启tcpsyncookies功能(即使半连接队列满,非异常请求也可以正常建立); 开启syncookies功能就可以在不使用SYN半连接队列的情况下成功建立连接,在前面我们源码分析也可以看到这点,当开启了syncookies功能就不会丢弃连接。 syncookies是这么做的:服务器根据当前状态计算出一个值,放在己方发出的SYNACK报文中发出,当客户端返回ACK报文时,取出该值验证,如果合法,就认为连接建立成功,如下图所示。 syncookies参数主要有以下三个值: 0值,表示关闭该功能; 1值,表示仅当SYN半连接队列放不下时,再启用它; 2值,表示无条件开启功能; 那么在应对SYN攻击时,只需要设置为1即可:echo1procsysnetipv4tcpsyncookies 方法三:减少SYNACK重传次数。 当服务端受到SYN攻击时,就会有大量处于SYNREVC状态的TCP连接,处于这个状态的TCP会重传SYNACK,当重传超过次数达到上限后,就会断开连接。 那么针对SYN攻击的场景,我们可以减少SYNACK的重传次数,以加快处于SYNREVC状态的TCP连接断开。echo1procsysnetipv4tcpsyncookies5、常见故障三:大量TIMEWAIT状态连接 Timewait状态存在的意义: 经过2MSL这个时间(在Linux系统里2MSL默认是60秒),足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。 等待足够的时间以确保最后的ACK能让被动关闭方接收,从而帮助其正常关闭。 在请求数比较大的场景下,可能会看到大量处于TIMEWAIT状态的连接,过多的TIMEWAIT状态主要的危害有两种: 第一是内存资源占用; 第二是对端口资源的占用,一个TCP连接至少消耗一个本地端口; 第二个危害是会造成严重的后果的,要知道,端口资源也是有限的,一般可以开启的端口为3276861000,也可以通过参数net。ipv4。iplocalportrange设置指定。 这时,我们可以优化与TIMEWAIT状态相关的内核选项,比如采取下面几种措施。 1、增大处于TIMEWAIT状态的连接数量net。ipv4。tcpmaxtwbuckets,并增大连接跟踪表的大小net。netfilter。nfconntrackmax(net。ipv4。tcpmaxtwbuckets值默认为18000,当系统中处于TIMEWAIT的连接一旦超过这个值时,系统就会将所有的TIMEWAIT连接状态重置,这个方法过于暴力,而且治标不治本) 2、减小net。ipv4。tcpfintimeout和net。netfilter。nfconntracktcptimeouttimewait,让系统尽快释放它们所占用的资源。 3、开启端口复用net。ipv4。tcptwreuse。这样,被TIMEWAIT状态占用的端口,还能用到新建的连接中。 4、增大本地端口的范围net。ipv4。iplocalportrange。这样就可以支持更多连接,提高整体的并发能力。 5、增加最大文件描述符的数量。你可以使用fs。nropen和fs。filemax,分别增大进程和系统的最大文件描述符数;或在应用程序的systemd配置文件中,配置LimitNOFILE,设置应用程序的最大文件描述符数。6、常见故障四:Keepalive不符合预期 TCP有一个机制是保活机制。这个机制的原理是这样的: 定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP保活机制会开始作用,每隔一个时间间隔,发送一个探测报文,该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的TCP连接已经死亡,系统内核将错误信息通知给上层应用程序。 在Linux内核可以有对应的参数可以设置保活时间、保活探测的次数、保活探测的时间间隔,以下都为默认值: tcpkeepalivetime7200:表示保活时间是7200秒(2小时),也就2小时内如果没有任何连接相关的活动,则会启动保活机制 tcpkeepaliveintvl75:表示每次检测间隔75秒; tcpkeepaliveprobes9:表示检测9次无响应,认为对方是不可达的,从而中断本次的连接。 也就是说在Linux系统中,最少需要经过2小时11分15秒才可以发现一个死亡连接。 这个时间是有点长的,我们也可以根据实际的需求,对以上的保活相关的参数进行设置。 1、缩短最后一次数据包到Keepalive探测包的间隔时间net。ipv4。tcpkeepalivetime; 2、缩短发送Keepalive探测包的间隔时间net。ipv4。tcpkeepaliveintvl; 3、减少Keepalive探测失败后,一直到通知应用程序前的重试次数net。ipv4。tcpkeepaliveprobes。 如果开启了TCP保活,需要考虑以下几种情况: 第一种,对端程序是正常工作的。当TCP保活的探测报文发送给对端,对端会正常响应,这样TCP保活时间会被重置,等待下一个TCP保活时间的到来。 第二种,对端程序崩溃并重启。当TCP保活的探测报文发送给对端后,对端是可以响应的,但由于没有该连接的有效信息,会产生一个RST报文,这样很快就会发现TCP连接已经被重置。 第三种,是对端程序崩溃,或对端由于其他原因导致报文不可达。当TCP保活的探测报文发送给对端后,石沉大海,没有响应,连续几次,达到保活探测次数后,TCP会报告该TCP连接已经死亡。7、TCP优化参数总结 优化TCP性能时,要注意,如果同时使用不同优化方法,可能会产生冲突。 比如,就像网络请求延迟的案例中,服务器端开启Nagle算法,而客户端开启延迟确认机制,就很容易导致网络延迟增大。 另外,在使用NAT的服务器上,如果开启net。ipv4。tcptwrecycle,就很容易导致各种连接失败。实际上,由于坑太多,这个选项在内核的4。1版本中已经删除了。 8、UDP常见优化方案 UDP提供了面向数据报的网络协议,它不需要网络连接,也不提供可靠性保障。所以,UDP优化,相对于TCP来说,要简单得多。这里我也总结了常见的几种优化方案。增大套接字缓冲区大小以及UDP缓冲区范围;跟前面TCP部分提到的一样,增大本地端口号的范围;根据MTU大小,调整UDP数据包的大小,减少或者避免分片的发生。 原文出处:https:mp。weixin。qq。comsfQ7xfnCUVRyw1JCgQ1zASQ