如何找出PHP进程占用CPU高的幕后元凶

业务反馈在进入页面时遇到白屏,无法正常打开浏览器页面进行学习。经检查发现,PHP进程的CPU占用率异常高。考虑到本周末的使用人数人数应该不会很大,理论上系统资源的使用不应如此之高。

因此,技术团队的第一反应是某个PHP进程可能陷入了死循环,导致资源占用过高。

排查

Step1 通过 htop 命令

过 htop 命令查看CPU和内存占用情况。按 Shift + M 键按内存使用量排序。

图片

由上图可见,可以看出占用CPU最高的TOP 2是 http://0.0.0.0:8782 RestyService 这个服务。

这两个进程CPU占用率接近81.2%。CPU时间片主要是被1747217487这两个进程给吃掉了, 所以目标锁定在1747217487这两个线程。

Step2 查看进程状态

通过命令php start.php status查看进程状态

图片

可以看出进程[17487]和[17475]状态进程状态[busy][busy]状态代表是繁忙

如果进程进入短暂的繁忙是正常情况,如果进程一直是繁忙状态,则有可能发生了业务阻塞或者业务死循环的情况。PS:idle代表空闲。

Step3 调试[busy]繁忙进程

有时候我们通过php start.php status 命令能看到有busy状态的进程,说明对应进程正在处理业务。

正常情况下业务处理完毕对应进程会恢复为idle状态,这一般情况下不会有什么问题。

但是如果一直是busy状态没有恢复过idle状态,则说明进程内的业务有阻塞或者无限循环,可以通过以下方法定位。

status里找到busy进程的pid,这里就拿17487这个进程来排查。

Step4 strace 跟踪进程

挑选一个进程pid(这里选择17487),获取当前进程中各进程的调用栈。

运行 sudo strace -ttp 17487 显示如下

图片

可以看到进程在不断的循环poll([{fd=9, events=....的系统调用,这是在等待fd9的描述符可读事件,也就是在等这个描述符返回数据。

注意: 如果没有显示任何系统调用,保留当前终端,重新再打开一个终端,运行kill -SIGALRM 17487(给进程发送一个闹钟信号),然后看strace的终端是否有响应,是否阻塞在某个系统调用上。如果仍然没有显示任何系统调用说明程序很可能处于业务死循环中。

Step5 lsof 查看进程描述符

 lsof -p $pid | grep $fd 查看进程在等待哪个外部资源的返回

运行 lsof -p 17487 | grep 9 显示如下

图片

进程描述符9对应的是9u的记录(最后一行),能看fd=9的描述符是一个tcp连接。

远程地址是172.18.207.82:mysql,说明进程应该是在访问一个数据库资源,循环poll([{fd=9, events=....是一直在等待数据库服务端返回数据,这解释了为什么进程处于busy状态。

确保一下是否是否数据库连接主机

ping rm-xxxxxxxxxxx.mysql.rds.aliyuncs.com
PING rm-xxxxxxxxxxx.mysql.rds.aliyuncs.com (172.18.207.82) 56(84) bytes of data.
64 bytes from 172.18.207.82 (172.18.207.82): icmp_seq=1 ttl=102 time=1.93 ms
64 bytes from 172.18.207.82 (172.18.207.82): icmp_seq=2 ttl=102 time=1.91 ms
64 bytes from 172.18.207.82 (172.18.207.82): icmp_seq=3 ttl=102 time=1.90 ms

通过上述htop中能看到busy进程占用cpu很高(81%)。基本断定代码里有无限死循环,在通过strace查看系统调用,可以看出是一条SQL在执行一个死循环

`sendto(9,"} \0\0\0\26SELECT id FROM sg_organ"...., 129 MSG DONTWAIT, NULL,0 = 129`

Step6 排查死循环SQL

排查导致死循环的SQL语句时。while 循环是一个常见的潜在问题源。

不过,死循环不仅限于 while 循环,还可能由其他类型的循环结构或不当的触发器使用引起。但在此,我们主要聚焦于 while 循环导致的死循环问题。

最终定位到死循环代码

图片

通过代码最终定位到的SQL语句

SELECT `id` FROM `sg_organ_xxx` WHERE  `id` = 2025

验证

while 循环代码修改提交部署重新部署后,重新登录服务器查看项目CPU和内存占用情况

图片

此时PHP进程的CPU占用已经恢复正常了!

来源:开源技术小栈

THE END