一整套线上故障排查技巧,爱了( 五 )


一整套线上故障排查技巧,爱了文章插图
如上图所示 , 这里有两个队列:

  • syns queue(半连接队列)
  • accept queue(全连接队列)
三次握手 , 在 server 收到 client 的 syn 后 , 把消息放到 syns queue , 回复 syn+ack 给 client , server 收到 client 的 ack 。
如果这时 accept queue 没满 , 那就从 syns queue 拿出暂存的信息放入 accept queue 中 , 否则按 tcp_abort_on_overflow 指示的执行 。
tcp_abort_on_overflow 0 表示如果三次握手第三步的时候 accept queue 满了那么 server 扔掉 client 发过来的 ack 。
tcp_abort_on_overflow 1 则表示第三步的时候如果全连接队列满了 , server 发送一个 RST 包给 client , 表示废掉这个握手过程和这个连接 , 意味着日志里可能会有很多 connection reset/connection reset by peer 。
那么在实际开发中 , 我们怎么能快速定位到 TCP 队列溢出呢?
netstat 命令 , 执行 netstat -s | egrep "listen|LISTEN":
一整套线上故障排查技巧,爱了文章插图
如上图所示 , overflowed 表示全连接队列溢出的次数 , sockets dropped 表示半连接队列溢出的次数 。
ss 命令 , 执行 ss -lnt:
一整套线上故障排查技巧,爱了文章插图
上面看到 Send-Q 表示第三列的 Listen 端口上的全连接队列最大为 5 , 第一列 Recv-Q 为全连接队列当前使用了多少 。
接着我们看看怎么设置全连接、半连接队列大小吧:全连接队列的大小取决于 min(backlog , somaxconn) 。
Backlog 是在 Socket 创建的时候传入的 , somaxconn 是一个 OS 级别的系统参数 。 而半连接队列的大小取决于 max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog) 。
在日常开发中 , 我们往往使用 Servlet 容器作为服务端 , 所以我们有时候也需要关注容器的连接队列大小 。
在 Tomcat 中 backlog 叫做 acceptCount , 在 Jetty 里面则是 acceptQueueSize 。
③RST 异常
RST 包表示连接重置 , 用于关闭一些无用的连接 , 通常表示异常关闭 , 区别于四次挥手 。
在实际开发中 , 我们往往会看到 connection reset/connection reset by peer 错误 , 这种情况就是 RST 包导致的 。
端口不存在:如果像不存在的端口发出建立连接 SYN 请求 , 那么服务端发现自己并没有这个端口则会直接返回一个 RST 报文 , 用于中断连接 。
主动代替 FIN 终止连接:一般来说 , 正常的连接关闭都是需要通过 FIN 报文实现 , 然而我们也可以用 RST 报文来代替 FIN , 表示直接终止连接 。
实际开发中 , 可设置 SO_LINGER 数值来控制 , 这种往往是故意的 , 来跳过 TIMED_WAIT , 提供交互效率 , 不闲就慎用 。
客户端或服务端有一边发生了异常 , 该方向对端发送 RST 以告知关闭连接:我们上面讲的 TCP 队列溢出发送 RST 包其实也是属于这一种 。
这种往往是由于某些原因 , 一方无法再能正常处理请求连接了(比如程序崩了 , 队列满了) , 从而告知另一方关闭连接 。
接收到的 TCP 报文不在已知的 TCP 连接内:比如 , 一方机器由于网络实在太差 TCP 报文失踪了 , 另一方关闭了该连接 , 然后过了许久收到了之前失踪的 TCP 报文 , 但由于对应的 TCP 连接已不存在 , 那么会直接发一个 RST 包以便开启新的连接 。
一方长期未收到另一方的确认报文 , 在一定时间或重传次数后发出 RST 报文
这种大多也和网络环境相关了 , 网络环境差可能会导致更多的 RST 报文 。
之前说过 RST 报文多会导致程序报错 , 在一个已关闭的连接上读操作会报 connection reset , 而在一个已关闭的连接上写操作则会报 connection reset by peer 。
通常我们可能还会看到 broken pipe 错误 , 这是管道层面的错误 , 表示对已关闭的管道进行读写 , 往往是在收到 RST , 报出 connection reset 错后继续读写数据报的错 , 这个在 glibc 源码注释中也有介绍 。
我们在排查故障时候怎么确定有 RST 包的存在呢?当然是使用 tcpdump 命令进行抓包 , 并使用 wireshark 进行简单分析了 。
tcpdump -i en0 tcp -w xxx.cap , en0 表示监听的网卡:
一整套线上故障排查技巧,爱了文章插图
接下来我们通过 wireshark 打开抓到的包 , 可能就能看到如下图所示 , 红色的就表示 RST 包了 。
一整套线上故障排查技巧,爱了文章插图
④TIME_WAIT 和 CLOSE_WAIT
TIME_WAIT 和 CLOSE_WAIT 是啥意思相信大家都知道 。