孤独酒馆|6000+字,30+张图。JAVA线上故障排查全套路总结( 六 )


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"
孤独酒馆|6000+字,30+张图。JAVA线上故障排查全套路总结如上图所示 , overflowed表示全连接队列溢出的次数 , sockets dropped表示半连接队列溢出的次数 。
ss命令 , 执行ss -lnt
孤独酒馆|6000+字,30+张图。JAVA线上故障排查全套路总结上面看到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包导致的 。
【孤独酒馆|6000+字,30+张图。JAVA线上故障排查全套路总结】端口不存在
如果像不存在的端口发出建立连接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表示监听的网卡 。


推荐阅读