高流量大并发Linux TCP性能调优( 四 )


从问题的描述来看 , 有点像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握手过程中建连接的流程和队列
 

高流量大并发Linux 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次)
 
高流量大并发Linux TCP性能调优

文章插图
 
如果TCP连接队列溢出 , 有哪些指标可以看呢?
上述解决过程有点绕 , 听起来懵 , 那么下次再出现类似问题有什么更快更明确的手段来确认这个问题呢?(通过具体的、感性的东西来强化我们对知识点的理解和吸收 。)
netstat -s
 
高流量大并发Linux TCP性能调优

文章插图
 
比如上面看到的 667399 times  , 表示全连接队列溢出的次数 , 隔几秒钟执行下 , 如果这个数字一直在增加的话肯定全连接队列偶尔满了 。
ss 命令
 
高流量大并发Linux TCP性能调优

文章插图
 
上面看到的第二列Send-Q 值是50 , 表示第三列的listen端口上的全连接队列最大为50 , 第一列Recv-Q为全连接队列当前使用了多少 。
全连接队列的大小取决于:min(backlog, somaxconn) . backlog是在socket创建的时候传入的 , somaxconn是一个os级别的系统参数 。
这个时候可以跟我们的代码建立联系了 , 比如Java创建ServerSocket的时候会让你传入backlog的值:

【高流量大并发Linux TCP性能调优】


推荐阅读