昨天晚上,有个业务出现异常,现象是:
1、nginx无法连接上游的php-fpm(nginx和php-fpm之间是本地的unix domain socket);
2、php-fpm进程跑满了,strace进程发现所有的子进程都阻塞在read系统调用上,lsof发现read的fd是一个和mysql的连接;
3、通过日志发现执行sql语句出错,错误码是2013,错误信息是Lost connection to MySQL server during query,并且每个请求的处理时间都在7875秒左右。
因此这里有3个疑问:
1、为什么php-fpm不能建立新的连接了?
2、php.ini里设置了max_execution_time是30秒,为什么每个请求的处理时间超过了7875秒?max_execution_time没有生效?
3、每个请求的处理时间为什么是7875秒,这个时间太长了,都干嘛了?
结论:
1、第一问题很好解释,因为php-fpm配置了进程上限,然后每个子进程都阻塞在了read系统调用上,所有无法接受新的连接请求了;
2、查了下资料发现php的max_execution_time不包括mysql请求处理的时间,这里感觉php太坑了:
3、为什么每个请求处理的时间是7875秒左右呢?这是由于网络原因导致的,php给mysql发送一个查询请求,然后php阻塞在read系统调用上,这时候如果php和mysql之间的网络断了、网络拥塞导致丢包或mysql server所在服务器内核panic了,那么php和mysql之间的网络连接就断开了,但是由于php阻塞在了read系统调用上,php对网络连接的异常是没有感知的,所以php会一直阻塞。直到tcp keepalive机制探测后才会发现网络的异常,read才会返回。tcp
keepalive相关的有三个配置:
配置项
默认值
含义
tcp_keepalive_time
7200
tcp开始进行keealive探测的秒数
tcp_keepalive_probes
9
探测失败时的重试次数
tcp_keepalive_intvl
75
每次重试的间隔
这3个配置的详细信息也可以通过man 7 tcp查看。
7200 + 75 * 9 = 7875
所以,php阻塞在read系统调用后,要7875秒左右之后才能根据tcp的keepalive机制探测到连接的丢失,read系统调用才会返回,所以请求的处理时间会是7875秒左右。
规避措施:
1、业务和mysql server最好不要跨机房部署;
2、由于max_execution_time并不包含mysql query的时间,因此在做数据库查询等相关的操作时,一定要设置连接、读写的超时时间;
3、跨机房拷贝文件的时候一定要限速,防止网络拥塞进而影响业务。
以上,感谢您的阅读,欢迎评论。