因此,蛰剑写下这篇文章,希望借此能把这个问题说清楚。欢迎大家一起互换磋商。
问题描述
场景:JAVA的client和server,利用socket通信。server利用NIO。

1.间歇性得涌现client向server建立连接三次握手已经完成,但server的selector没有相应到这连接。
2.出问题的韶光点,会同时有很多连接涌现这个问题。
3.selector没有销毁重修,一贯用的都是一个。
4.程序刚启动的时候必会涌现一些,之后会间歇性涌现。
剖析问题
正常TCP建连接三次握手过程:
第一步:client 发送 syn 到server 发起握手;第二步:server 收到 syn后回答syn+ack给client;第三步:client 收到syn+ack后,回答server一个ack表示收到了server的syn+ack(此时client的56911端口的连接已经是established)。
从问题的描述来看,有点像TCP建连接的时候全连接行列步队(accept行列步队,后面详细讲)满了,尤其是症状2、4. 为了证明是这个缘故原由,立时通过 netstat -s | egrep \"大众listen\"大众 去看行列步队的溢出统计数据:
反复看了几次之后创造这个overflowed 一贯在增加,那么可以明确的是server上全连接行列步队一定溢出了。
接着查看溢出后,OS怎么处理:
tcp_abort_on_overflow 为0表示如果三次握手第三步的时候全连接行列步队满了那么server扔掉client 发过来的ack(在server端认为连接还没建立起来)
为了证明客户端运用代码的非常跟全连接行列步队满有关系,我先把tcp_abort_on_overflow修正成 1,1表示第三步的时候如果全连接行列步队满了,server发送一个reset包给client,表示废掉这个握手过程和这个连接(本来在server端这个连接就还没建立起来)。
接着测试,这时在客户端非常中可以看到很多connection reset by peer的缺点,到此证明客户端缺点是这个缘故原由导致的(逻辑严谨、快速证明问题的关键点所在)。
于是开拓同学翻看java 源代码创造socket 默认的backlog(这个值掌握全连接行列步队的大小,后面再详述)是50,于是改大重新跑,经由12个小时以上的压测,这个缺点一次都没涌现了,同时不雅观察到 overflowed 也不再增加了。
到此问题办理,大略来说TCP三次握手后有个accept行列步队,进到这个行列步队才能从Listen变成accept,默认backlog 值是50,很随意马虎就满了。满了之后握手第三步的时候server就忽略了client发过来的ack包(隔一段韶光server重发握手第二步的syn+ack包给client),如果这个连接一贯排不上队就非常了。
但是不能只是知足问题的办理,而是要去复盘办理过程,中间涉及到了哪些知识点是我所缺失落或者理解不到位的;这个问题除了上面的非常信息表现出来之外,还有没有更明确地指征来查看和确认这个问题。
深入理解TCP握手过程中建连接的流程和行列步队
如上图所示,这里有两个行列步队:syns queue(半连接行列步队);accept queue(全连接行列步队)。
三次握手中,在第一步server收到client的syn后,把这个连接信息放到半连接行列步队中,同时回答syn+ack给client(第二步);
第三步的时候server收到client的ack,如果这时全连接行列步队没满,那么从半连接行列步队拿出这个连接的信息放入到全连接行列步队中,否则按tcp_abort_on_overflow指示的实行。
这时如果全连接行列步队满了并且tcp_abort_on_overflow是0的话,server过一段韶光再次发送syn+ack给client(也便是重新走握手的第二步),如果client超时等待比较短,client就很随意马虎非常了。
在我们的os中retry 第二步的默认次数是2(centos默认是5次):
如果TCP连接行列步队溢出,有哪些指标可以看呢?
上述办理过程有点绕,听起来懵,那么下次再涌现类似问题有什么更快更明确的手段来确认这个问题呢?(通过详细的、感性的东西来强化我们对知识点的理解和接管。)
netstat -s
比如上面看到的 667399 times ,表示全连接行列步队溢出的次数,隔几秒钟实行下,如果这个数字一贯在增加的话肯定全连接行列步队偶尔满了。
ss 命令
上面看到的第二列Send-Q 值是50,表示第三列的listen端口上的全连接行列步队最大为50,第一列Recv-Q为全连接行列步队当前利用了多少。
全连接行列步队的大小取决于:min(backlog, somaxconn) . backlog是在socket创建的时候传入的,somaxconn是一个os级别的系统参数。
这个时候可以跟我们的代码建立联系了,比如Java创建ServerSocket的时候会让你传入backlog的值:
(来自JDK帮助文档:https://docs.oracle.com/javase/7/docs/api/java/net/ServerSocket.html)
半连接行列步队的大小取决于:max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog),不同版本的os会有些差异。
我们写代码的时候从来没有想过这个backlog或者说大多时候就没给他值(那么默认便是50),直接忽略了他,首先这是一个知识点的盲点;其次大概哪天你在哪篇文章中看到了这个参数,当时有点印象,但是过一阵子就忘了,这是知识之间没有建立连接,不是体系化的。但是如果你跟我一样首先经历了这个问题的痛楚,然后在压力和痛楚的驱动自己去找为什么,同时能够把为什么从代码层推理理解到OS层,那么这个知识点你才算是比较好地节制了,也会成为你的知识体系在TCP或者性能方面发展自我成长的一个有力抓手。
netstat 命令
netstat跟ss命令一样也能看到Send-Q、Recv-Q这些状态信息,不过如果这个连接不是Listen状态的话,Recv-Q便是指收到的数据还在缓存中,还没被进程读取,这个值便是还没被进程读取的 bytes;而 Send 则是发送行列步队中没有被远程主机确认的 bytes 数。
netstat -tn 看到的 Recv-Q 跟全连接半连接没有关系,这里特意拿出来说一下是由于随意马虎跟 ss -lnt 的 Recv-Q 搞稠浊,顺便建立知识体系,巩固干系知识点 。
比如如下netstat -t 看到的Recv-Q有大量数据堆积,那么一样平常是CPU处理不过来导致的:
上面是通过一些详细的工具、指标来认识全连接行列步队(工程效率的手段)。
实践验证一下上面的理解
把java中backlog改成10(越小越随意马虎溢出),连续跑压力,这个时候client又开始报非常了,然后在server上通过 ss 命令不雅观察到:
按照前面的理解,这个时候我们能看到3306这个端口上的做事全连接行列步队最大是10,但是现在有11个在行列步队中和等待进行列步队的,肯定有一个连接进不去行列步队要overflow掉,同时也确实能看到overflow的值在不断地增大。
Tomcat和Nginx中的Accept行列步队参数
Tomcat默认短连接,backlog(Tomcat里面的术语是Accept count)Ali-tomcat默认是200, Apache Tomcat默认100。
Nginx默认是511
由于Nginx是多进程模式,以是看到了多个8085,也便是多个进程都监听同一个端口以只管即便避免高下文切换来提升性能
总结
全连接行列步队、半连接行列步队溢出这种问题很随意马虎被忽略,但是又很关键,特殊是对付一些短连接运用(比如Nginx、PHP,当然他们也是支持长连接的)更随意马虎爆发。 一旦溢出,从cpu、线程状态看起来都比较正常,但是压力上不去,在client看来rt也比较高(rt=网络+排队+真正做事韶光),但是从server日志记录的真正做事韶光来看rt又很短。
jdk、netty等一些框架默认backlog比较小,可能有些情形下导致性能上不去。
希望通过本文能够帮大家理解TCP连接过程中的半连接行列步队和全连接行列步队的观点、事理和浸染,更关键的是有哪些指标可以明确看到这些问题(工程效率帮助强化对理论的理解)。
其余每个详细问题都是最好学习的机会,光看书理解肯定是不足深刻的,请珍惜每个详细问题,碰到后能够把来龙去脉弄清楚,每个问题都是你对详细知识点通关的好机会。
末了提出干系问题给大家思考
全连接行列步队满了会影响半连接行列步队吗?netstat -s看到的overflowed和ignored的数值有什么联系吗?如果client走完了TCP握手的第三步,在client看来连接已经建立好了,但是server上的对应连接实际没有准备好,这个时候如果client发数据给server,server会怎么处理呢?(有同学说会reset,你以为呢?)提出这些问题,希望以这个知识点为抓手,让你的知识体系开始自我成长。