记录一次排查PHP脚本执行卡住的问题


Posted in PHP onDecember 27, 2016

发现问题

最近忽然从监控中发现,我们一个服务的一台机器负载比同机房的其他机器要高,而流入流出流量没有差别,进一步查看发现每个机房都有一台机器存在相同的现象,梳理后发现有问题的这些机器相比正常的机器多跑了一些PHP脚本,于是猜测是执行脚本出问题导致。

解决问题

登录机器后执行top命令,果然发现存在一个CPU占用较高的PHP进程,然后执行下列命令,发现存在一个由crontab启动的执行了很长时间的PHP脚本:

ps aux | grep 'php' | grep -v 'php-fpm'

由于之前也遇到过PHP脚本执行卡住的类似情况,当时的怀疑是跨机房的Mysql查询在网络抖动时导致Mysql连接卡住了,于是理所当然的将所有卡住的进程都kill掉了,再从负载上看机器马上就恢复正常了,于是心满意足的跑去干别的了。

过了一段时间,刷了下监控,发现问题又出现了,注释掉crontab并kill掉进程后,手动执行问题脚本,竟然能稳定复现问题!看来是把问题想得太简单了,尝试用strace命令看下卡住的进程当前究竟在干什么:

[tabalt@localhost ~] sudo strace -p 13793
Process 13793 attached - interrupt to quit

什么输出都没有!再用netstat看下这个进程是否打开了什么端口:

[tabalt@localhost ~] sudo netstat -tunpa | grep 13793
tcp  0  0 192.168.1.100:38019  192.168.1.101:3306  ESTABLISHED 13793/php
tcp  0  0 192.168.1.100:47107  192.168.1.102:6379  CLOSE_WAIT 13793/php

可以看到进程打开了两个端口,分别与Mysql和Redis建立了连接,并且处于连接建立(ESTABLISHED)和对方主动关闭连接(CLOSE_WAIT)的状态;初看确实像是和数据库的连接卡住了,但是因为吃过亏上过当,咱们使用tcpdump抓包看进程和数据库之间的交互:

tcpdump -i eth0 host 192.168.1.101 and port 3306 -w ~/mysql.cap

抓了好一会,~/mysql.cap 文件中却也没有任何输出,难道进程和Mysql之间已经没有任何交互了?那为什么连接建立没有关闭呢?看来只能从头追踪一下脚本的执行情况了:

首先为了能来得及strace到进程,在PHP脚本最开始的时候输出进程的pid并sleep 10s:

echo getmypid(); sleep(10);

然后启动tcpdump准备抓包本机和Mysql的交互过程。

最后执行PHP脚本,并复制输出的pid后在新窗口中执行strace命令。

这下strace和tcpdump都有内容了!从strace结果看recvfrom之后不再有poll,但并没有看出来有什么不对:

//...
poll([{fd=4, events=POLLIN|POLLERR|POLLHUP}], 1, 1471228928) = 1 ([{fd=4, revents=POLLIN}])
recvfrom(4, "://xxx.com/\0\0\23jiadia"..., 271, MSG_DONTWAIT, NULL, NULL) = 271
poll([{fd=4, events=POLLIN|POLLERR|POLLHUP}], 1, 1471228928) = 1 ([{fd=4, revents=POLLIN}])
recvfrom(4, "_b?ie=UTF8&node=658390051\0\0008www."..., 271, MSG_DONTWAIT, NULL, NULL) = 206

再从抓包结果看,执行了两条SQL查询语句之后,进程没有再次发送查询请求的包,从程序记录SQL语句日志中,也发现确实只执行了两条:

select * from sites where type = 1 limit 50;
select * from sites where type = 2 limit 50;

但从这些现象中,仍然没有能看出任何端倪,只好祭出终极大法:输出调试!大概看了下代码,并在关键地方添加输出语句,于是代码看起来如下:

echo("start foreach\n");
foreach($types as $type)
{
 echo("foreach $type\n");
 $result[$type] = $this->getSites($type);
}
echo("end foreach\n");

执行后输出如下,查询type为2的网址时卡住了:

start foreach
foreach 1
foreach 2

开始怀疑调用的getSites()方法有问题,代码如下:

$sites = array(); // 省略从数据库查询的代码
$siteNum = 8;  // 省略从配置读的代码
$urlKeys = $result = array();
for($i = 0; $i < $siteNum; $i++)
{
 do {
  $site = array_shift($sites);
  $urlKey = md5($site['url']);
 } while(array_key_exists($urlKey, $urlKeys));

 $urlKeys[$urlKey] = 1;
 $result[] = $site;
}
return $result;

原来这里为了实现拿8个不重复的网址写了2个循环,如果结果中不重复的网址只有7个就会有一个空,少于7个就会有死循环!于是查了下type为2的网址个数,果然是只有6个!

总结

该问题从发现到解决花了大概1天时间,虽然最后证明是低级的代码BUG导致,但是整个排查过程还是挺有收获的,最开始的想当然证明是非常肤浅的,过程中tcpdump和strace的结果也已经很能说明问题了,对各个工具的应用应该要更加熟练,工具的结果也要深入分析。以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。

PHP 相关文章推荐
php中批量修改文件后缀名的函数代码
Oct 23 PHP
PHP查询网站的PR值
Oct 30 PHP
php多种形式发送邮件(mail qmail邮件系统 phpmailer类)
Jan 22 PHP
php禁止直接从浏览器输入地址访问.php文件的方法
Nov 04 PHP
php图片处理函数获取类型及扩展名实例
Nov 19 PHP
从wamp到xampp的升级之路
Apr 08 PHP
php阿拉伯数字转中文人民币大写
Dec 21 PHP
php生成带logo二维码方法小结
Apr 08 PHP
PHP生成各种随机验证码的方法总结【附demo源码】
Jun 05 PHP
微信公众号实现扫码获取微信用户信息(网页授权)
Apr 09 PHP
ThinkPHP5&amp;5.1框架关联模型分页操作示例
Aug 03 PHP
PHP如何通过带尾指针的链表实现'队列'
Oct 22 PHP
PHP串行化与反串行化实例分析
Dec 27 #PHP
PHP 表单提交及处理表单数据详解及实例
Dec 27 #PHP
iOS+PHP注册登录系统 PHP部分(上)
Dec 26 #PHP
PHP实现表单提交时去除斜杠的方法
Dec 26 #PHP
PHP简单实现冒泡排序的方法
Dec 26 #PHP
php mysql操作mysql_connect连接数据库实例详解
Dec 26 #PHP
PHP常用函数总结(180多个)
Dec 25 #PHP
You might like
西德产收音机
2021/03/01 无线电
《PHP编程最快明白》第五讲:php目录、文件操作
2010/11/01 PHP
php关于array_multisort多维数组排序的使用说明
2011/01/04 PHP
教你识别简单的免查杀PHP后门
2015/09/13 PHP
php查询内存信息操作示例
2019/05/09 PHP
div当滚动到页面顶部的时候固定在顶部实例代码
2013/05/27 Javascript
jquery submit ie6下失效的原因分析及解决方法
2013/11/15 Javascript
深入理解JavaScript系列(47):对象创建模式(上篇)
2015/03/04 Javascript
JavaScript使用Prototype实现面向对象的方法
2015/04/14 Javascript
如何防止JavaScript自动插入分号
2015/11/05 Javascript
Eclipse编辑jsp、js文件时卡死现象的解决办法汇总
2016/02/02 Javascript
jQuery实现订单提交页发送短信功能前端处理方法
2016/07/04 Javascript
js转html实体的方法
2016/09/27 Javascript
JavaScript用JSONP跨域请求数据实例详解
2017/01/06 Javascript
js Canvas绘制圆形时钟教程
2017/02/06 Javascript
Mongoose实现虚拟字段查询的方法详解
2017/08/15 Javascript
基于vue实现圆形菜单栏组件
2019/07/05 Javascript
详解基于Vue/React项目的移动端适配方案
2019/08/23 Javascript
Vue 利用指令实现禁止反复发送请求的两种方法
2019/09/15 Javascript
从零开始在vue-cli4配置自适应vw布局的实现
2020/06/08 Javascript
解决vue项目打包上服务器显示404错误,本地没出错的问题
2020/11/03 Javascript
Vue实现购物车基本功能
2020/11/08 Javascript
js实现筛选功能
2020/11/24 Javascript
Pandas DataFrame 取一行数据会得到Series的方法
2018/11/10 Python
Python弹出输入框并获取输入值的实例
2019/06/18 Python
python 字典操作提取key,value的方法
2019/06/26 Python
python 实现生成均匀分布的点
2019/12/05 Python
Python通过VGG16模型实现图像风格转换操作详解
2020/01/16 Python
如何在django中运行scrapy框架
2020/04/22 Python
Pytorch转tflite方式
2020/05/25 Python
提高python代码运行效率的一些建议
2020/09/29 Python
汇集了世界上最好的天然和有机美容产品:LoveLula
2018/02/05 全球购物
CheapTickets泰国:廉价航班,查看促销价格并预订机票
2019/12/28 全球购物
世界上最大的皮肤科医生拥有和经营的美容网站:LovelySkin
2021/01/03 全球购物
PHP基本语法
2021/03/31 PHP
Python实现的扫码工具居然这么好用!
2021/06/07 Python