关于网络编程:不为人知的网络编程十一从底层入手深度分析TCP连接耗时的秘密

本文作者张彦飞,原题“聊聊TCP连贯耗时的那些事儿”,有少许改变。

1、引言

对于基于互联网的通信利用(比方IM聊天、推送零碎),数据传递时应用TCP协定绝对较多。这是因为在TCP/IP协定簇的传输层协定中,TCP协定具备牢靠的连贯、谬误重传、拥塞管制等长处,所以目前在利用场景上比UDP更宽泛一些。

置信你也肯定听闻过TCP也存在一些毛病,能常都是陈词滥调的开销要略大。然而各路技术博客里都在单单说开销大、或者开销小,而少见不给出具体的量化剖析。不客气的讲,相似阐述都是没什么养分的废话。

通过日常工作的思考之后,我更想弄明确的是,TCP的开销到底有多大,是否进行量化。一条TCP连贯的建设须要耗时提早多少,是多少毫秒,还是多少微秒?能不能有一个哪怕是粗略的量化预计?当然影响TCP耗时的因素有很多,比方网络丢包等等。我明天只分享我在工作实际中遇到的比拟高发的各种状况。

写在后面:得益于Linux内核的开源,本文中所提及的底层以及具体的内核级代码例子,都是以Linux零碎为例。

学习交换:

  • 挪动端IM开发入门文章:《新手入门一篇就够:从零开发挪动端IM》
  • 开源IM框架源码:https://github.com/JackJiang2…(备用地址点此)

(本文已同步公布于:http://www.52im.net/thread-32…)

2、系列文章

本文是系列文章中的第11篇,本系列文章的纲要如下:

《鲜为人知的网络编程(一):浅析TCP协定中的疑难杂症(上篇)》
《鲜为人知的网络编程(二):浅析TCP协定中的疑难杂症(下篇)》
《鲜为人知的网络编程(三):敞开TCP连贯时为什么会TIME_WAIT、CLOSE_WAIT》
《鲜为人知的网络编程(四):深入研究剖析TCP的异样敞开》
《鲜为人知的网络编程(五):UDP的连接性和负载平衡》
《鲜为人知的网络编程(六):深刻地了解UDP协定并用好它》
《鲜为人知的网络编程(七):如何让不牢靠的UDP变的牢靠?》
《鲜为人知的网络编程(八):从数据传输层深度解密HTTP》
《鲜为人知的网络编程(九):实践联系实际,全方位深刻了解DNS》
《鲜为人知的网络编程(十):深刻操作系统,从内核了解网络包的接管过程(Linux篇)》
《鲜为人知的网络编程(十一):从底层动手,深度剖析TCP连贯耗时的机密》(本文)
《鲜为人知的网络编程(十二):彻底搞懂TCP协定层的KeepAlive保活机制》
《鲜为人知的网络编程(十三):深刻操作系统,彻底搞懂127.0.0.1本机网络通信》
《鲜为人知的网络编程(十四):拔掉网线再插上,TCP连贯还在吗?一文即懂!》

3、现实状况下的TCP连贯耗时剖析

要想搞清楚TCP连贯的耗时,咱们须要具体理解连贯的建设过程。

在前文《深刻操作系统,从内核了解网络包的接管过程(Linux篇)》中咱们介绍了数据包在接收端是怎么被接管的:数据包从发送方进去,通过网络达到接管方的网卡;在接管方网卡将数据包DMA到RingBuffer后,内核通过硬中断、软中断等机制来解决(如果发送的是用户数据的话,最初会发送到socket的接管队列中,并唤醒用户过程)。

在软中断中,当一个包被内核从RingBuffer中摘下来的时候,在内核中是用struct sk_buff构造体来示意的(参见内核代码include/linux/skbuff.h)。其中的data成员是接管到的数据,在协定栈逐层被解决的时候,通过批改指针指向data的不同地位,来找到每一层协定关怀的数据。

对于TCP协定包来说,它的Header中有一个重要的字段-flags。

如下图:

通过设置不同的标记位,将TCP包分成SYNC、FIN、ACK、RST等类型:

1)客户端通过connect零碎调用命令内核收回SYNC、ACK等包来实现和服务器TCP连贯的建设;
2)在服务器端,可能会接管许许多多的连贯申请,内核还须要借助一些辅助数据结构-半连贯队列和全连贯队列。
咱们来看一下整个连贯过程:

在这个连贯过程中,咱们来简略剖析一下每一步的耗时:

1)客户端收回SYNC包:客户端个别是通过connect零碎调用来收回SYN的,这里牵涉到本机的零碎调用和软中断的CPU耗时开销;
2)SYN传到服务器:SYN从客户端网卡被收回,开始“跨过山和大海,也穿过三三两两……”,这是一次短途远距离的网络传输;
3)服务器解决SYN包:内核通过软中断来收包,而后放到半连贯队列中,而后再收回SYN/ACK响应。又是CPU耗时开销;
4)SYC/ACK传到客户端:SYC/ACK从服务器端被收回后,同样跨过很多山、可能很多大海来到客户端。又一次短途网络跋涉;
5)客户端解决SYN/ACK:客户端内核收包并解决SYN后,通过几us的CPU解决,接着收回ACK。同样是软中断解决开销;
6)ACK传到服务器:和SYN包,一样,再通过简直同样远的路,传输一遍。 又一次短途网络跋涉;
7)服务端收到ACK:服务器端内核收到并解决ACK,而后把对应的连贯从半连贯队列中取出来,而后放到全连贯队列中。一次软中断CPU开销;
8)服务器端用户过程唤醒:正在被accpet零碎调用阻塞的用户过程被唤醒,而后从全连贯队列中取出来曾经建设好的连贯。一次上下文切换的CPU开销。

以上几步操作,能够简略划分为两类:

第一类:是内核耗费CPU进行接管、发送或者是解决,包含零碎调用、软中断和上下文切换。它们的耗时根本都是几个us左右;
第二类:是网络传输,当包被从一台机器上收回当前,两头要通过各式各样的网线、各种交换机路由器。所以网络传输的耗时相比本机的CPU解决,就要高的多了。依据网络远近个别在几ms~到几百ms不等。

1ms就等于1000us,因而网络传输耗时比双端的CPU开销要高1000倍左右,甚至更高可能还到100000倍。

所以:在失常的TCP连贯的建设过程中,个别思考网络延时即可。

PS:一个RTT指的是包从一台服务器到另外一台服务器的一个来回的延迟时间。

所以从全局来看:TCP连贯建设的网络耗时大概须要三次传输,再加上少许的单方CPU开销,总共大概比1.5倍RTT大一点点。

不过,从客户端视角来看:只有ACK包收回了,内核就认为连贯是建设胜利了。所以如果在客户端打点统计TCP连贯建设耗时的话,只须要两次传输耗时-既1个RTT多一点的工夫。(对于服务器端视角来看同理,从SYN包收到开始算,到收到ACK,两头也是一次RTT耗时)。

4、极其状况下的TCP连贯耗时剖析

上一节能够看到:在客户端视角,失常状况下一次TCP连贯总的耗时也就就大概是一次网络RTT的耗时。如果所有的事件都这么简略,我想我的这次分享也就没有必要了。事件不肯定总是这么美妙,意外的产生在劫难逃。

在某些状况下,可能会导致TCP连贯时的网络传输耗时上涨、CPU解决开销减少、甚至是连贯失败。本节将就我在线上遇到过的各种切身体会的沟沟坎坎,来剖析一下极其状况下的TCP连贯耗时状况。

4.1 客户端connect调用耗时失控案例
失常一个零碎调用的耗时也就是几个us(微秒)左右。然而在我的《追踪将服务器CPU耗光的凶手!》一文中,笔者的一台服务器过后遇到一个情况:某次运维同学转达过去说该服务CPU不够用了,须要扩容。

过后的服务器监控如下图:

该服务之前始终每秒抗2000左右的qps,CPU的idel始终有70%+,怎么忽然就CPU一下就不够用了呢。

而且更奇怪的是CPU被打到谷底的那一段时间,负载却并不高(服务器为4核机器,负载3-4是比拟失常的)。

起初通过排查当前发现当TCP客户端TIME_WAIT有30000左右,导致可用端口不是特地短缺的时候,connect零碎调用的CPU开销间接上涨了100多倍,每次耗时达到了2500us(微秒),达到了毫秒级别。

当遇到这种问题的时候,尽管TCP连贯建设耗时只减少了2ms左右,整体TCP连贯耗时看起来还可承受。但这里的问题在于这2ms多都是在耗费CPU的周期,所以问题不小。

解决起来也非常简单,方法很多:批改内核参数net.ipv4.ip_local_port_range多预留一些端口号、改用长连贯都能够。

4.2 TCP半/全连贯队列满的案例
如果连贯建设的过程中,任意一个队列满了,那么客户端发送过去的syn或者ack就会被抛弃。客户端期待很长一段时间无果后,而后会收回TCP Retransmission重传。

拿半连贯队列举例:

要晓得的是下面TCP握手超时重传的工夫是秒级别的。也就是说一旦server端的连贯队列导致连贯建设不胜利,那么光建设连贯就至多须要秒级以上。而失常的在同机房的状况下只是不到1毫秒的事件,整整高了1000倍左右。

尤其是对于给用户提供实时服务的程序来说,用户体验将会受到较大影响。如果连重传也没有握手胜利的话,很可能等不及二次重试,这个用户拜访间接就超时了。

还有另外一个更坏的状况是:它还有可能会影响其它的用户。

如果你应用的是过程/线程池这种模型提供服务,比方:php-fpm。咱们晓得fpm过程是阻塞的,当它响应一个用户申请的时候,该过程是没有方法再响应其它申请的。如果你开了100个过程/线程,而某一段时间内有50个过程/线程卡在和redis或者mysql服务器的握手连贯上了(留神:这个时候你的服务器是TCP连贯的客户端一方)。这一段时间内相当于你能够用的失常工作的过程/线程只有50个了。而这个50个worker可能基本解决不过去,这时候你的服务可能就会产生拥挤。再继续略微工夫长一点的话,可能就产生雪崩了,整个服务都有可能会受影响。

既然结果有可能这么重大,那么咱们如何查看咱们手头的服务是否有因为半/全连贯队列满的状况产生呢?

在客户端:能够抓包查看是否有SYN的TCP Retransmission。如果有偶发的TCP Retransmission,那就阐明对应的服务端连贯队列可能有问题了。

在服务端的话:查看起来就更不便一些了。netstat -s 可查看到以后零碎半连贯队列满导致的丢包统计,但该数字记录的是总丢包数。你须要再借助 watch 命令动静监控。如果上面的数字在你监控的过程中变了,那阐明以后服务器有因为半连贯队列满而产生的丢包。你可能须要加大你的半连贯队列的长度了。

$ watch’netstat -s | grep LISTEN’

8 SYNs to LISTEN sockets ignored

对于全连贯队列来说呢,查看办法也相似:

$ watch’netstat -s | grep overflowed’

160 timesthe listen queue of a socket overflowed

如果你的服务因为队列满产生丢包,其中一个做法就是加大半/全连贯队列的长度。 半连贯队列长度Linux内核中,次要受tcp_max_syn_backlog影响 加大它到一个适合的值就能够。

cat /proc/sys/net/ipv4/tcp_max_syn_backlog

1024

echo “2048” > /proc/sys/net/ipv4/tcp_max_syn_backlog

全连贯队列长度是应用程序调用listen时传入的backlog以及内核参数net.core.somaxconn二者之中较小的那个。你可能须要同时调整你的应用程序和该内核参数。

cat /proc/sys/net/core/somaxconn

128

echo “256” > /proc/sys/net/core/somaxconn

改完之后咱们能够通过ss命令输入的Send-Q确认最终失效长度:

$ ss -nlt

Recv-Q Send-Q Local Address:Port Address:Port

0 128 :80 :*

Recv-Q通知了咱们以后该过程的全连贯队列应用长度状况。如果Recv-Q曾经迫近了Send-Q,那么可能不须要等到丢包也应该筹备加大你的全连贯队列了。

如果加大队列后依然有十分偶发的队列溢出的话,咱们能够暂且容忍。

如果依然有较长时间解决不过去怎么办?

另外一个做法就是间接报错,不要让客户端超时期待。

例如将Redis、Mysql等后端接口的内核参数tcp_abort_on_overflow为1。如果队列满了,间接发reset给client。通知后端过程/线程不要薄情地傻等。这时候client会收到谬误“connection reset by peer”。就义一个用户的拜访申请,要比把整个站都搞崩了还是要强的。

5、TCP连贯耗时实测剖析

5.1 测试前的筹备
我写了一段非常简单的代码,用来在客户端统计每创立一个TCP连贯须要耗费多长时间。

<?php

$ip= {服务器ip};

$port= {服务器端口};

$count= 50000;

function buildConnect($ip,$port,$num){

for($i=0;$i<$num;$i++){

    $socket= socket_create(AF_INET,SOCK_STREAM,SOL_TCP);

    if($socket==false) {

        echo"$ip $port socket_create() 失败的起因是:".socket_strerror(socket_last_error($socket))."\n";

        sleep(5);

        continue;

    }



    if(false == socket_connect($socket, $ip, $port)){

        echo"$ip $port socket_connect() 失败的起因是:".socket_strerror(socket_last_error($socket))."\n";

        sleep(5);

        continue;

    }

    socket_close($socket);

}

}

$t1= microtime(true);

buildConnect($ip, $port, $count);

echo(($t2-$t1)*1000).’ms’;

在测试之前,咱们须要本机linux可用的端口数短缺,如果不够50000个,最好调整短缺。

echo “5000 65000” /proc/sys/net/ipv4/ip_local_port_range

5.2 失常状况下的测试
留神:无论是客户端还是服务器端都不要抉择有线上服务在跑的机器,否则你的测试可能会影响失常用户拜访

首先:我的客户端位于河北怀来的IDC机房内,服务器抉择的是公司广东机房的某台机器。执行ping命令失去的提早大概是37ms,应用上述脚本建设50000次连贯后,失去的连贯均匀耗时也是37ms。

这是因为后面咱们说过的,对于客户端来看,第三次的握手只有包发送进来,就认为是握手胜利了,所以只须要一次RTT、两次传输耗时。尽管这两头还会有客户端和服务端的零碎调用开销、软中断开销,但因为它们的开销失常状况下只有几个us(微秒),所以对总的连贯建设延时影响不大。

接下来:我换了一台指标服务器,该服务器所在机房位于北京。离怀来有一些间隔,然而和广东比起来可要近多了。这一次ping进去的RTT是1.6~1.7ms左右,在客户端统计建设50000次连贯后算出每条连贯耗时是1.64ms。

再做一次试验:这次选中试验的服务器和客户端间接位于同一个机房内,ping提早在0.2ms~0.3ms左右。跑了以上脚本当前,试验后果是50000 TCP连贯总共耗费了11605ms,均匀每次须要0.23ms。

线上架构提醒:这里看到同机房提早只有零点几ms,然而跨个间隔不远的机房,光TCP握手耗时就涨了4倍。如果再要是跨地区到广东,那就是百倍的耗时差距了。线上部署时,现实的计划是将本人服务依赖的各种mysql、redis等服务和本人部署在同一个地区、同一个机房(再变态一点,甚至能够是甚至是同一个机架)。因为这样包含TCP链接建设啥的各种网络包传输都要快很多。要尽可能防止短途跨地区机房的调用状况呈现。

5.3 TCP连贯队列溢出状况下的测试
测试完了跨地区、跨机房和跨机器。这次为了快,间接和本机建设连贯后果会咋样呢?

Ping本机ip或127.0.0.1的提早大略是0.02ms,本机ip比其它机器RTT必定要短。我感觉必定连贯会十分快,嗯试验一下。

间断建设5W TCP连贯:总工夫耗费27154ms,均匀每次须要0.54ms左右。

嗯!?怎么比跨机器还长很多?

有了后面的实践根底,咱们应该想到了:因为本机RTT太短,所以霎时连贯建设申请量很大,就会导致全连贯队列或者半连贯队列被打满的状况。一旦产生队列满,过后撞上的那个连贯申请就得须要3秒+的连贯建设延时。所以下面的试验后果中,均匀耗时看起来比RTT高很多。

在试验的过程中,我应用tcpdump抓包看到了上面的一幕。原来有少部分握手耗时3s+,起因是半连贯队列满了导致客户端期待超时后进行了SYN的重传。

咱们又从新改成每500个连贯,sleep 1秒。嗯好,终于没有卡的了(或者也能够加大连贯队列长度)。

论断是:本机50000次TCP连贯在客户端统计总耗时102399 ms,减去sleep的100秒后,均匀每个TCP连贯耗费0.048ms。比ping提早略高一些。

这是因为当RTT变的足够小的时候,内核CPU耗时开销就会显现出来了,另外TCP连贯要比ping的icmp协定更简单一些,所以比ping提早略高0.02ms左右比拟失常。

6、本文小结

TCP连贯在建设异样的状况下,可能须要好几秒,一个害处就是会影响用户体验,甚至导致以后用户拜访超时都有可能。另外一个害处是可能会诱发雪崩。

所以当你的服务器应用短连贯的形式拜访数据的时候:肯定要学会要监控你的服务器的连贯建设是否有异样状态产生。如果有,学会优化掉它。当然你也能够采纳本机内存缓存,或者应用连接池来放弃长连贯,通过这两种形式间接防止掉TCP握手挥手的各种开销也能够。

再说失常状况下:TCP建设的延时大概就是两台机器之间的一个RTT耗时,这是防止不了的。然而你能够管制两台机器之间的物理间隔来升高这个RTT,比方把你要拜访的redis尽可能地部署的离后端接口机器近一点,这样RTT也能从几十ms削减到最低可能零点几ms。

最初咱们再思考一下:如果咱们把服务器部署在北京,给纽约的用户拜访可行吗?

后面的咱们同机房也好,跨机房也好,电信号传输的耗时根本能够疏忽(因为物理间隔很近),网络提早基本上是转发设施占用的耗时。然而如果是逾越了半个地球的话,电信号的传输耗时咱们可得算一算了。 北京到纽约的球面间隔大略是15000公里,那么抛开设施转发提早,仅仅光速流传一个来回(RTT是Rround trip time,要跑两次),须要工夫 = 15,000,000 *2 / 光速 = 100ms。理论的提早可能比这个还要大一些,个别都得200ms以上。建设在这个提早上,要想提供用户能拜访的秒级服务就很艰难了。所以对于海内用户,最好都要在当地建机房或者购买海内的服务器。

附录:更多网络编程精髓材料

[1] 网络编程(根底)材料:

《TCP/IP详解 – 第17章·TCP:传输控制协议》
《技术往事:扭转世界的TCP/IP协定(宝贵多图、手机慎点)》
《通俗易懂-深刻了解TCP协定(上):实践根底》
《实践经典:TCP协定的3次握手与4次挥手过程详解》
《P2P技术详解(一):NAT详解——具体原理、P2P简介》
《网络编程懒人入门(一):疾速了解网络通信协定(上篇)》
《网络编程懒人入门(二):疾速了解网络通信协定(下篇)》
《网络编程懒人入门(三):疾速了解TCP协定一篇就够》
《网络编程懒人入门(四):疾速了解TCP和UDP的差别》
《网络编程懒人入门(五):疾速了解为什么说UDP有时比TCP更有劣势》
《网络编程懒人入门(六):史上最艰深的集线器、交换机、路由器性能原理入门》
《网络编程懒人入门(七):深入浅出,全面了解HTTP协定》
《网络编程懒人入门(八):手把手教你写基于TCP的Socket长连贯》
《网络编程懒人入门(九):艰深解说,有了IP地址,为何还要用MAC地址?》
《网络编程懒人入门(十):一泡尿的工夫,疾速读懂QUIC协定》
《网络编程懒人入门(十一):一文读懂什么是IPv6》
《网络编程懒人入门(十二):疾速读懂Http/3协定,一篇就够!》
《网络编程懒人入门(十三):一泡尿的工夫,疾速搞懂TCP和UDP的区别》
《网络编程懒人入门(十四):到底什么是Socket?一文即懂!》
《技术扫盲:新一代基于UDP的低延时网络传输层协定——QUIC详解》
《让互联网更快:新一代QUIC协定在腾讯的技术实际分享》
《聊聊iOS中网络编程长连贯的那些事》
《IPv6技术详解:基本概念、利用现状、技术实际(上篇)》
《IPv6技术详解:基本概念、利用现状、技术实际(下篇)》
《Java对IPv6的反对详解:反对状况、相干API、演示代码》
《从HTTP/0.9到HTTP/2:一文读懂HTTP协定的历史演变和设计思路》
《脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手》
《脑残式网络编程入门(二):咱们在读写Socket时,到底在读写什么?》
《脑残式网络编程入门(三):HTTP协定必知必会的一些常识》
《脑残式网络编程入门(四):疾速了解HTTP/2的服务器推送(Server Push)》
《脑残式网络编程入门(五):每天都在用的Ping命令,它到底是什么?》
《脑残式网络编程入门(六):什么是公网IP和内网IP?NAT转换又是什么鬼?》
《脑残式网络编程入门(七):面视必备,史上最艰深计算机网络分层详解》
《脑残式网络编程入门(八):你真的理解127.0.0.1和0.0.0.0的区别?》
《脑残式网络编程入门(九):面试必考,史上最艰深大小端字节序详解》
《迈向高阶:优良Android程序员必知必会的网络根底》
《Android程序员必知必会的网络通信传输层协定——UDP和TCP》
《技术大牛陈硕的分享:由浅入深,网络编程学习教训干货总结》
《可能会搞砸你的面试:你晓得一个TCP连贯上能发动多少个HTTP申请吗?》
《5G时代曾经到来,TCP/IP老矣,尚能饭否?》

更多同类文章 ……
[2] 网络编程(高阶)材料:

《高性能网络编程(一):单台服务器并发TCP连接数到底能够有多少》
《高性能网络编程(二):上一个10年,驰名的C10K并发连贯问题》
《高性能网络编程(三):下一个10年,是时候思考C10M并发问题了》
《高性能网络编程(四):从C10K到C10M高性能网络应用的实践摸索》
《高性能网络编程(五):一文读懂高性能网络编程中的I/O模型》
《高性能网络编程(六):一文读懂高性能网络编程中的线程模型》
《高性能网络编程(七):到底什么是高并发?一文即懂!》
《IM开发者的零根底通信技术入门(十):零根底,史上最强5G技术扫盲》
《IM开发者的零根底通信技术入门(十一):为什么WiFi信号差?一文即懂!》
《IM开发者的零根底通信技术入门(十二):上网卡顿?网络掉线?一文即懂!》
《IM开发者的零根底通信技术入门(十三):为什么手机信号差?一文即懂!》
《IM开发者的零根底通信技术入门(十四):高铁上无线上网有多难?一文即懂!》
《IM开发者的零根底通信技术入门(十五):了解定位技术,一篇就够》
《以网游服务端的网络接入层设计为例,了解实时通信的技术挑战》
《知乎技术分享:知乎千万级并发的高性能长连贯网关技术实际》
《淘宝技术分享:手淘亿级挪动端接入层网关的技术演进之路》

更多同类文章 ……
(本文已同步公布于:http://www.52im.net/thread-32…)

【腾讯云】云产品限时秒杀,爆款1核2G云服务器,首年50元

阿里云限时活动-2核2G-5M带宽-60G SSD-1000G月流量 ,特惠价99元/年(原价1234.2元/年,可以直接买3年),速抢

本文由乐趣区整理发布,转载请注明出处,谢谢。

您可能还喜欢...

发表回复

您的电子邮箱地址不会被公开。

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据