【php】PHP进程卡死和MySQL超时时间的设置方法
PHP进程卡死和MySQL超时时间的设置方法
ljfrocky发布于 2020-11-18
前言
最近线上一台服务器的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_WAITtcp 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进程是卡在了某个系统调用:
再使用lsof -np PHP进程ID
命令查看进程打开了哪些文件资源,发现有一个状态为ESTABLISHED
的mysql的连接:
猜想会不会是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);
45.113.192.102
是百度的一台web服务器,跟80端口肯定是能建立TCP连接的,但它不是mysql服务器,所以建立连接后,也肯定不会发送greeting packet
给PHP,正符合我要测试的场景。执行这段代码后,PHP确实会卡住在real_connect处,而且不会超时,跟线上情况一模一样。
解决方法
那怎样才能针对这种场景设置一个超时时间呢,在这里我走了弯路,曾尝试使用set_time_limit
、default_socket_timeout
、MYSQLI_OPT_CONNECT_TIMEOUT
等参数来设置超时时间,但均没有生效,一度使我十分苦恼。后面才意识到这种场景的超时是属于读写超时,不是连接超时,因此使用MYSQLI_OPT_CONNECT_TIMEOUT
来设置是无效的,这参数控制的是连接超时。而set_time_limit
控制的仅是PHP脚本自身的执行时间,不包括系统调用、数据库操作所消耗的时间,因此也不会生效,这一点在PHP文档里也有说明:
下面是正确答案,有两种方法:
1)修改mysqlnd.net_read_timeout
配置项
在php.ini
里添加一个mysqlnd.net_read_timeout
的配置项即可,例如mysqlnd.net_read_timeout = 60
就代表将mysql的读写超时时间设置为60秒
2)通过MYSQLI_OPT_READ_TIMEOUT
常量来配置,这个常量在PHP 7.2(含)版本才添加,因此需要PHP版本大于等于7.2,代码示例:
$mysql = new mysqli();$mysql->options(MYSQLI_OPT_CONNECT_TIMEOUT, 5); // 设置连接超时未5秒
$mysql->options(MYSQLI_OPT_READ_TIMEOUT, 60); // 设置读写超时为60秒
$mysql->real_connect('45.113.192.102', 'root', 'xxx', 'xxx', 80);
这种方法相比第一种影响范围更小,它只影响当前连接,而第一种因为是通过修改配置文件实现的,因此它会影响所有使用同一配置文件的其它PHP程序。
设置好读写超时后,重新执行测试代码,也确实没有卡死了,60秒后就会超时报错。
总结
针对mysql连接,一个完整的超时设置,应该同时设置连接超时和读写超时,如果仅仅设置了连接超时,那么在一些特殊情景下,PHP进程是有可能会被卡死的
phpmysql超时卡死
阅读 194发布于 2020-11-18
本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议
ljfrocky
90后程序猿,PHPer
360 声望
0 粉丝
ljfrocky
90后程序猿,PHPer
360 声望
0 粉丝
宣传栏
目录
前言
最近线上一台服务器的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_WAITtcp 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进程是卡在了某个系统调用:
再使用lsof -np PHP进程ID
命令查看进程打开了哪些文件资源,发现有一个状态为ESTABLISHED
的mysql的连接:
猜想会不会是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);
45.113.192.102
是百度的一台web服务器,跟80端口肯定是能建立TCP连接的,但它不是mysql服务器,所以建立连接后,也肯定不会发送greeting packet
给PHP,正符合我要测试的场景。执行这段代码后,PHP确实会卡住在real_connect处,而且不会超时,跟线上情况一模一样。
解决方法
那怎样才能针对这种场景设置一个超时时间呢,在这里我走了弯路,曾尝试使用set_time_limit
、default_socket_timeout
、MYSQLI_OPT_CONNECT_TIMEOUT
等参数来设置超时时间,但均没有生效,一度使我十分苦恼。后面才意识到这种场景的超时是属于读写超时,不是连接超时,因此使用MYSQLI_OPT_CONNECT_TIMEOUT
来设置是无效的,这参数控制的是连接超时。而set_time_limit
控制的仅是PHP脚本自身的执行时间,不包括系统调用、数据库操作所消耗的时间,因此也不会生效,这一点在PHP文档里也有说明:
下面是正确答案,有两种方法:
1)修改mysqlnd.net_read_timeout
配置项
在php.ini
里添加一个mysqlnd.net_read_timeout
的配置项即可,例如mysqlnd.net_read_timeout = 60
就代表将mysql的读写超时时间设置为60秒
2)通过MYSQLI_OPT_READ_TIMEOUT
常量来配置,这个常量在PHP 7.2(含)版本才添加,因此需要PHP版本大于等于7.2,代码示例:
$mysql = new mysqli();$mysql->options(MYSQLI_OPT_CONNECT_TIMEOUT, 5); // 设置连接超时未5秒
$mysql->options(MYSQLI_OPT_READ_TIMEOUT, 60); // 设置读写超时为60秒
$mysql->real_connect('45.113.192.102', 'root', 'xxx', 'xxx', 80);
这种方法相比第一种影响范围更小,它只影响当前连接,而第一种因为是通过修改配置文件实现的,因此它会影响所有使用同一配置文件的其它PHP程序。
设置好读写超时后,重新执行测试代码,也确实没有卡死了,60秒后就会超时报错。
总结
针对mysql连接,一个完整的超时设置,应该同时设置连接超时和读写超时,如果仅仅设置了连接超时,那么在一些特殊情景下,PHP进程是有可能会被卡死的
以上是 【php】PHP进程卡死和MySQL超时时间的设置方法 的全部内容, 来源链接: utcz.com/a/111758.html
得票时间