记录一次排查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 中的输出缓冲
Dec 21 PHP
PHP下判断网址是否有效的代码
Oct 08 PHP
用PHP实现 上一篇、下一篇的代码
Sep 29 PHP
php inc文件使用的风险和注意事项
Nov 12 PHP
PHP中使用glob函数实现一句话删除某个目录下的所有文件
Jul 22 PHP
php实现的zip文件内容比较类
Sep 24 PHP
php过滤表单提交的html等危险代码
Nov 03 PHP
PHP实现的随机IP函数【国内IP段】
Jul 20 PHP
php版微信小店调用api示例代码
Nov 12 PHP
PHP获取ttf格式文件字体名的方法示例
Mar 06 PHP
php post换行的方法
Feb 03 PHP
PHP函数用法详解【初始化、嵌套、内置函数等】
Jun 02 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
PHP 杂谈《重构-改善既有代码的设计》之三 重新组织数据
2012/04/09 PHP
PHP连接SQLServer2005的实现方法(附ntwdblib.dll下载)
2012/07/02 PHP
PHP中mysql_field_type()函数用法
2014/11/24 PHP
主流PHP框架的优缺点对比分析
2014/12/25 PHP
php+mysqli实现批量执行插入、更新及删除数据的方法
2015/01/29 PHP
PHP htmlspecialchars_decode()函数用法讲解
2019/03/01 PHP
php设计模式之备忘模式分析【星际争霸游戏案例】
2020/03/24 PHP
IE中直接运行显示当前网页中的图片 推荐
2006/08/31 Javascript
JavaScript DOM 学习第五章 表单简介
2010/02/19 Javascript
深入探寻seajs的模块化与加载方式
2015/04/14 Javascript
JavaScript 数组- Array的方法总结(推荐)
2016/07/21 Javascript
浅谈jQuery中的eq()与DOM中element.[]的区别
2016/10/28 Javascript
微信小程序 动态绑定事件并实现事件修改样式
2017/04/13 Javascript
微信小程序 如何引入外部字体库iconfont的图标
2018/01/31 Javascript
element el-input directive数字进行控制
2018/10/11 Javascript
NProgress显示顶部进度条效果及使用详解
2019/09/21 Javascript
VUE中使用HTTP库Axios方法详解
2020/02/05 Javascript
vue监听键盘事件的相关总结
2021/01/29 Vue.js
[05:39]2014DOTA2西雅图国际邀请赛 淘汰赛7月14日TOPPLAY
2014/07/14 DOTA
python thread 并发且顺序运行示例
2009/04/09 Python
Python守护进程用法实例分析
2015/06/04 Python
搞笑的程序猿:看看你是哪种Python程序员
2015/06/12 Python
利用python打印出菱形、三角形以及矩形的方法实例
2017/08/08 Python
Python入门之三角函数全解【收藏】
2017/11/08 Python
Python基础教程之if判断,while循环,循环嵌套
2019/04/25 Python
对python 中re.sub,replace(),strip()的区别详解
2019/07/22 Python
python中的线程threading.Thread()使用详解
2019/12/17 Python
Python如何读写CSV文件
2020/08/13 Python
毕业生找工作推荐信
2013/11/21 职场文书
出纳岗位职责范本
2013/12/01 职场文书
研究生考核个人自我鉴定
2014/03/27 职场文书
法制宣传日活动总结
2014/04/29 职场文书
2014年生产部工作总结
2014/12/17 职场文书
导游词之藏龙百瀑景区
2019/12/30 职场文书
如何给HttpServletRequest增加消息头
2021/06/30 Java/Android
2021年国产动漫公司排行前十名,玄机科技上榜,第二推出过铠甲勇士
2022/03/18 杂记