【概述】
这两天排查解决了一个问题,问题的解决其实很简单,但是整个分析过程还是很有意义的,本文对整个分析过程以及问题如何解决进行总结。
【问题现象】
我们的xxx服务依赖zk,服务在启动之前会检测zk是否处于提供服务的状态,确保启动后可以正确操作zk而不至于异常退出。
具体通过如下命令获取zk的状态:
echo stat | nc 192.168.73.77 2181
出现问题时,发现nc命令一直没有返回,导致无法执行后续的步骤(程序压根没启动)。
【问题排查】
看到问题,第一反应是手动执行一次nc命令,看看是否正常,当然,结果没有令人失望,完全正常。不信邪,再多试几次,nc命令均正确返回退出,并且能获取到对应的状态信息,看来是个偶现问题。
既然命令当前执行都正常,难道是执行nc命令的那个时刻,zk出现了异常导致没有响应?到zk上查看了对应的日志,也没有发现对应时间段有错误的打印。
既然zk都没有错误日志信息,那只能先分析下nc命令当前卡在哪里了。
顺着这个思路,先netstat看了下nc的连接情况,发现与zk的连接处于FIN_WAIT2状态。
熟悉TCP四次挥手的应该都知道,FIN_WAIT2是主动关闭的一方没有收到对端的FIN,从而处于FIN_WAIT2状态(状态变化如下图所示)
对于对端没有对socket关闭的情况,可以快速编写服务端demo进行验证。
例如执行下面脚本:
#!/usr/bin/env python
import socket
import time
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', 50000))
s.listen(1)
c,_ = s.accept()
msg=c.recv(1024)
print(msg)
c.sendall('hncscwc')
time.sleep(1000)
c.close()
然后再执行命令,可以发现nc未返回,并且链接处于FIN_WAIT2的状态。
搞清楚了FIN_WAIT2,那么nc卡主和这个又有什么关系呢?带着疑问下载了nmap的源码,查看了下nc执行的相关流程。
内部处理流程本质上就是先建立tcp连接,然后循环处理socket上的可读可写事件,当有可读事件,并且长度为0(EOF)时,回调处理中标记退出循环,然后整个进程也就跟着退出了。
而长度为0的可读事件,是收到FIN后,内核协议栈往上发送的可读事件。
结合上面说的FIN_WAIT2,就可以知道nc命令为什么不退出了。
通过增加参数“+vvvvvv”查看nc命令执行过程中的输出,对比正常情况和异常情况,可以清楚的看到这一点:
正常退出的情况:
异常不退出的情况:
清楚了问题的所有环节,只剩下为什么nc命令没有收到zk发送的fin了,zk真的可能没有进行socket的关闭吗?还是因为网络问题导致了fin包丢失?
多次复现均未果,而zk的日志也无法提供有力帮助,监控也没有看出当时网络有较大的流量或严重丢包,问题的分析只能作罢。
【问题解决】
虽然不能最终定位是因为zk没有发送close还是因为异常导致了FIN包丢失,但问题终归还是要解决,因此只能从修改使用方式来考虑如何进行规避。
简单man了一把nc,发现有一个"-i"参数,指的是连接的最大读写空闲时间。加上参数,再来进行测试,发现连接虽然处于FIN_WAIT2状态,但等待指定时长后,nc命令返回退出了。
带着参数再看下命令执行过程的输出,发现增加了超时事件,结合源码分析,超时事件的回调处理中也会标记退出循环,从而进程最终也结束退出。
也就是说, "-i"参数是可以正确规避解决问题的。
【总结】
问题比较简单,相关基础知识的融会贯通有助于快速解决问题。另外,多使用man是一个好习惯。
原创不易,点赞,在看,分享是最好的支持, 谢谢~