阿里妹导读: 最近, 阿里中间件小哥哥蛰剑碰到一个问题 --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, 很容易就满了. 满了之后握手第三步的时候 server 就忽略了 client 发过来的 ack 包(隔一段时间 server 重发握手第二步的 syn+ack 包给 client), 如果这个连接一直排不上队就异常了.
但是不能只是满足问题的解决, 而是要去复盘解决过程, 中间涉及到了哪些知识点是我所缺失或者理解不到位的; 这个问题除了上面的异常信息表现出来之外, 还有没有更明确地指征来查看和确认这个问题.
深入理解 TCP 握手过程中建连接的流程和队列
- http://veithen.github.io/2014/01/01/how-tcp-backlog-works-in-linux.html
- http://www.cnblogs.com/zengkefu/p/5606696.html
- http://www.cnxct.com/something-about-phpfpm-s-backlog/
- http://jaseywang.me/2014/07/20/tcp-queue-的一些问题/
- http://jin-yang.github.io/blog/network-synack-queue.html#
- http://blog.chinaunix.net/uid-20662820-id-4154399.html
来源: https://juejin.im/entry/5b41d519e51d45195604e963