PHP项目CPU占用过高排查

wen PHP项目 4

PHP项目CPU占用过高?从定位到解决的完整排查指南(附实战命令)


目录导读

  1. 现象描述:CPU飙升的常见“假象”与真实信号
  2. 第一板斧:使用topstrace锁定“真凶”进程
  3. 第二板斧:深入PHP-FPM慢日志与xhprof性能分析
  4. 第三板斧:数据库/Redis交互瓶颈的逆向排查
  5. 高频问答:解决你最后的5个疑问
  6. 预防与总结:让CPU尖峰“胎死腹中”

现象描述
当你的top命令显示php-fpm进程CPU占用率超过80%,且持续数分钟不降时,请先别急着重启,很多情况下,这并非PHP代码本身“死循环”,而是外部调用阻塞(如MySQL查询无索引、Redis连接超时重试)导致的CPU空转,判断依据:若CPU占用率呈锯齿状波动,多为偶发请求触发;若呈直线拉满,则可能是代码逻辑死循环或恶意攻击。

PHP项目CPU占用过高排查


第一板斧:用topstrace定位“线程级”真凶

  1. 执行top -H -p $(pidof php-fpm | awk '{print $1}'),查看每个PHP-FPM工作线程的CPU占用。
  2. 找到CPU最高的线程PID(TID),例如12345
  3. 执行strace -p 12345 -c -f -o /tmp/strace.log,等待10秒后Ctrl+C,查看日志中系统调用耗时分布
    • 若大量pollselect,说明等待外部IO(Redis/MySQL)。
    • 若大量clock_gettimefutex,说明存在自旋锁或死循环。
  4. 实战技巧:如果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.logscript_filenametrace字段——若高频出现同一个function_name(如preg_matcharray_merge),说明该函数存在O(n²)复杂度问题。

性能分析神器xhprof

  1. 安装并启用扩展,在入口文件开启:
    xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);
    register_shutdown_function(function() {
     $data = xhprof_disable();
     // 保存$data到数据库,用xhprof_html展示
    });
  2. 重点观察wt(执行时间)和cpu(CPU周期)最高的前10个函数,通常发现JSON解析函数json_decode图片处理库Imagick占用极高——这往往是因传递了超大字符串或未压缩图片。

第三板斧:数据库与Redis的“隐形杀手”
案例:某电商项目CPU飙升,strace显示大量sendtorecvfrom到MySQL端口,用SHOW PROCESSLIST;发现大量Sending data状态SQL。
解法

  • 开启MySQL慢查询日志,找到Rows_examined超过1万行的SQL。
  • EXPLAIN分析是否未命中索引——通常加复合索引即可解决。
  • 若Redis连接超时,检查php.inidefault_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参数。


预防与总结

  1. 强制超时:在PHP入口文件顶部加set_time_limit(10),防止死循环拖垮机器。
  2. 队列化重任务:将图片处理、邮件发送改用Redis + Supervisor异步消费。
  3. 监控告警:部署Prometheus + node_exporter,当CPU>80%时自动抓取topstrace快照。
  4. 代码审查:避免在循环内做SELECT *,多用yield处理大数组。

最后的核心心法:CPU排查本质是“二分法”定位——先判断是用户态还是内核态,再判断是阻塞等待还是计算密集,掌握strace + xhprof这对组合拳,80%的CPU问题能在5分钟内水落石出。

抱歉,评论功能暂时关闭!