最近线上一台服务器的nginx总是会有一部分请求(不是所有请求)报upstream timed out (110: Connection timed out) while connecting to upstream的错误,看起来像是后端的phpcgi进程出问题了,但如果phpcgi进程有问题,不是应该所有请求都会报错才对么,于是展开排查。
排查原因在我们服务器上,PHP是使用9006端口进行监听的,执行netstat -an | grep 9006命令查看相关连接的网络状态,看到有一部分连接处于CLOSE_WAIT状态:
- tcp 0 0 10.0.0.188:9006 10.0.0.52:37316 CLOSE_WAIT
- tcp 0 0 10.0.0.188:9006 10.0.0.52:37292 CLOSE_WAIT
- tcp 0 0 10.0.0.188:9006 10.0.0.52:37300 CLOSE_WAIT
- tcp 0 0 10.0.0.188:9006 10.0.0.52:37302 CLOSE_WAIT
- tcp 0 0 10.0.0.188:9006 10.0.0.52:37234 CLOSE_WAIT
复制代码
奇怪的是,这些连接一直停留在CLOSE_WAIT的状态,不会变化,所以应该就是这些连接导致PHP进程被卡死,无法处理新的请求,从而导致部分请求报Connection timed out错误的。 根据TCP连接的四次挥手过程,CLOSE_WAIT状态是连接被关闭方才会出现的。nginx调用PHP,PHP响应太慢导致nginx超时,继而nginx主动关闭跟PHP的TCP连接,因此PHP进程是被关闭方,这个没啥问题。但PHP进程在收到nginx的关闭请求后,应该也跟着关闭才对,但实际没有,而是停留在了CLOSE_WAIT状态,说明PHP被某些东西卡住了,没办法关闭连接。 于是使用strace -p PHP进程ID查看PHP卡在了什么地方,等了一两分钟,strace命令什么东西都没输出,说明PHP进程是卡在了某个系统调用: 猜想会不会是mysql导致PHP卡死呢,于是根据显示的连接端口号10465,到mysql服务器上查看这个端口的连接情况,发现居然没有这个端口的TCP连接。这就神奇了,这里我只能猜测是:PHP成功跟mysql服务器建立TCP连接,但由于丢包或者防火墙拦截等奇怪的原因,PHP没有收到mysql的greeting packet,于是导致PHP一直在这里空等。后面mysql服务器主动断开TCP连接,但发送的FIN包也被拦截了,导致收不到PHP的ACK回应,于是mysql继续释放了这个连接。于是就出现了这个连接在PHP端是ESTABLISHED状态,但在mysql端却不存在这个连接的情况。 确认和模拟测试为了确认到底是不是mysql连接卡死了PHP,使用gdb -p PHP进程ID进行调试,上面lsof命令显示mysql的连接对应的文件描述符是5u,gdb里输入命令call close(5)强制把这个连接关闭,关闭后PHP进程的CLOSE_WAIT状态马上消失了,证明确实是这个连接卡住了PHP。 接着我尝试模拟“成功建立TCP连接,但收不到mysql greeting packet”的这种情景,看PHP会不会卡死,测试代码如下:
- $mysql = new mysqli();
- $mysql->real_connect('45.113.192.102', 'root', 'xxx', 'xxx', 80);
复制代码
|