阿里妹导读:最近,阿里中间件小哥哥蛰剑碰到一个问题——client端连接服务器总是抛异常。在反复定位分析、并查阅各种资料文章搞懂后,他发现没有文章把这两个队列以及怎么观察他们的指标说清楚。
因此,蛰剑写下这篇文章,希望借此能把这个问题说清楚。欢迎大家一起交流探讨。
问题描述
场景: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,很容易就满了。满了之后握手第三步的时候s