PHP项目CPU占用过高?从定位到解决的完整排查指南(附实战命令)
目录导读
- 现象描述:CPU飙升的常见“假象”与真实信号
- 第一板斧:使用
top与strace锁定“真凶”进程 - 第二板斧:深入PHP-FPM慢日志与
xhprof性能分析 - 第三板斧:数据库/Redis交互瓶颈的逆向排查
- 高频问答:解决你最后的5个疑问
- 预防与总结:让CPU尖峰“胎死腹中”
现象描述
当你的top命令显示php-fpm进程CPU占用率超过80%,且持续数分钟不降时,请先别急着重启,很多情况下,这并非PHP代码本身“死循环”,而是外部调用阻塞(如MySQL查询无索引、Redis连接超时重试)导致的CPU空转,判断依据:若CPU占用率呈锯齿状波动,多为偶发请求触发;若呈直线拉满,则可能是代码逻辑死循环或恶意攻击。

第一板斧:用top与strace定位“线程级”真凶
- 执行
top -H -p $(pidof php-fpm | awk '{print $1}'),查看每个PHP-FPM工作线程的CPU占用。 - 找到CPU最高的线程PID(TID),例如
12345。 - 执行
strace -p 12345 -c -f -o /tmp/strace.log,等待10秒后Ctrl+C,查看日志中系统调用耗时分布:- 若大量
poll或select,说明等待外部IO(Redis/MySQL)。 - 若大量
clock_gettime或futex,说明存在自旋锁或死循环。
- 若大量
- 实战技巧:如果
strace追踪不到,可用gdb -p 12345附加进程,执行bt查看PHP调用栈(需安装php-dev扩展)。
第二板斧:PHP-FPM慢日志与xhprof的“微型解剖”
慢日志能直接暴露执行超过阈值的请求:
# 编辑php-fpm.conf slowlog = /var/log/php-fpm/slow.log request_slowlog_timeout = 5s
观察slow.log中script_filename和trace字段——若高频出现同一个function_name(如preg_match或array_merge),说明该函数存在O(n²)复杂度问题。
性能分析神器xhprof:
- 安装并启用扩展,在入口文件开启:
xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY); register_shutdown_function(function() { $data = xhprof_disable(); // 保存$data到数据库,用xhprof_html展示 }); - 重点观察
wt(执行时间)和cpu(CPU周期)最高的前10个函数,通常发现JSON解析函数json_decode或图片处理库Imagick占用极高——这往往是因传递了超大字符串或未压缩图片。
第三板斧:数据库与Redis的“隐形杀手”
案例:某电商项目CPU飙升,strace显示大量sendto和recvfrom到MySQL端口,用SHOW PROCESSLIST;发现大量Sending data状态SQL。
解法:
- 开启MySQL慢查询日志,找到
Rows_examined超过1万行的SQL。 - 用
EXPLAIN分析是否未命中索引——通常加复合索引即可解决。 - 若Redis连接超时,检查
php.ini中default_socket_timeout是否过短,导致循环重连。
高频问答
Q1:为什么重启PHP-FPM后CPU恢复正常,但几分钟后又飙升?
A:重启只是“清空”了当前进程的畸形状态,但根本诱因(如某定时任务执行file_get_contents无超时设置)仍在,请重点查看crontab计划任务,加set_time_limit(30)和curl超时参数。
Q2:CPU占用高但PHP-FPM的慢日志没有任何记录?
A:慢日志只记录超过阈值的PHP执行时间,如果CPU高是因扩展(如opcache冲突)或系统调用卡死,需结合strace观察,另外检查opcache.enable_cli是否误开启。
Q3:用strace追踪不到任何系统调用,但CPU依然100%?
A:这属于用户态死循环,可用gdb打印PHP栈,或执行pmap 12345 | grep anon查看内存分配异常,更简单的方法:在代码中随机插入file_put_contents('/tmp/debug', microtime(), FILE_APPEND),看日志最后暂停在哪一行。
Q4:如何区分是“业务高峰期”还是“代码漏洞”?
A:用netstat -anp | grep :80 | wc -l统计并发连接数,若连接数>500且CPU时间分布均匀,属正常负载;若连接数只有50但CPU爆满,则是单请求脚本异常。
Q5:有没有一键检测工具?
A:perf top -p $(pidof php-fpm)可以实时显示内核级热点函数,但需安装linux-tools,若显示php内部符号为[unknown],请确保PHP编译时加了-g参数。
预防与总结
- 强制超时:在PHP入口文件顶部加
set_time_limit(10),防止死循环拖垮机器。 - 队列化重任务:将图片处理、邮件发送改用
Redis+Supervisor异步消费。 - 监控告警:部署
Prometheus + node_exporter,当CPU>80%时自动抓取top和strace快照。 - 代码审查:避免在循环内做
SELECT *,多用yield处理大数组。
最后的核心心法:CPU排查本质是“二分法”定位——先判断是用户态还是内核态,再判断是阻塞等待还是计算密集,掌握strace + xhprof这对组合拳,80%的CPU问题能在5分钟内水落石出。