记录一次排查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代码(星期六,星期日总和)
Nov 12 PHP
深入了解PHP类Class的概念
Jun 14 PHP
php中3des加密代码(完全与.net中的兼容)
Aug 02 PHP
PHP 透明水印生成代码
Aug 27 PHP
php curl获取网页内容(IPV6下超时)的解决办法
Jul 16 PHP
php+xml实现在线英文词典之添加词条的方法
Jan 23 PHP
php实现四舍五入的方法小结
Mar 03 PHP
PHP实现XML与数据格式进行转换类实例
Jul 29 PHP
PHP使用Pthread实现的多线程操作实例
Nov 14 PHP
PHP数组相加操作及与array_merge的区别浅析
Nov 26 PHP
PHP redis实现超迷你全文检索
Mar 04 PHP
PHP面向对象程序设计__tostring()和__invoke()用法分析
Jun 12 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
十大“创意”战术!
2020/03/04 星际争霸
百事可乐也出咖啡了 双倍咖啡因双倍快乐
2021/03/03 咖啡文化
一个PHP日历程序
2006/12/06 PHP
php使用百度翻译api示例分享
2014/01/31 PHP
php的webservice的wsdl的XML无法显示问题的解决方法
2014/03/11 PHP
PHP批量删除、清除UTF-8文件BOM头的代码实例
2014/04/14 PHP
php监测数据是否成功插入到Mysql数据库的方法
2016/11/25 PHP
Thinkphp5 自定义上传文件名的实现方法
2019/07/23 PHP
PHP正则之正向预查与反向预查讲解与实例
2020/04/06 PHP
Javascript实现返回上一页面并刷新的小例子
2013/12/11 Javascript
Javascript学习笔记之数组的构造函数
2014/11/23 Javascript
Javascript 拖拽雏形(逐行分析代码,让你轻松了拖拽的原理)
2015/01/23 Javascript
举例详解JavaScript中Promise的使用
2015/06/24 Javascript
JavaScript数据类型学习笔记
2016/01/25 Javascript
VUEJS 2.0 子组件访问/调用父组件的实例
2018/02/10 Javascript
React Router v4 入坑指南(小结)
2018/04/08 Javascript
解决微信小程序中转换时间格式IOS不兼容的问题
2019/02/15 Javascript
Layui点击图片弹框预览的实现方法
2019/09/16 Javascript
[00:36]TI7不朽珍藏III——斯温不朽展示
2017/07/15 DOTA
Python线程详解
2015/06/24 Python
Python实现将Excel转换为json的方法示例
2017/08/05 Python
tensorflow学习笔记之简单的神经网络训练和测试
2018/04/15 Python
对python .txt文件读取及数据处理方法总结
2018/04/23 Python
python 读取文本文件的行数据,文件.splitlines()的方法
2018/07/12 Python
解决pycharm 误删掉项目文件的处理方法
2018/10/22 Python
python Opencv计算图像相似度过程解析
2019/12/03 Python
CSS Grid布局教程之浏览器开启CSS Grid Layout汇总
2014/12/30 HTML / CSS
Nili Lotan官网:Nili Lotan同名品牌
2018/01/07 全球购物
美国最受欢迎的度假目的地优惠套餐:BookVIP
2018/09/27 全球购物
Hotels.com拉丁美洲:从豪华酒店到经济型酒店的预定优惠和折扣
2019/12/09 全球购物
护理毕业生自我鉴定
2014/02/11 职场文书
春节联欢晚会主持词
2014/03/24 职场文书
2014年行政执法工作总结
2014/12/11 职场文书
辩护词范文大全
2015/05/21 职场文书
对Golang中的FORM相关字段理解
2021/05/02 Golang
spring cloud gateway中如何读取请求参数
2021/07/15 Java/Android