本文目录导读:

- 方法一:使用 top + strace(最快定位“当前在干嘛”)
- 方法二:生成火焰图(终极武器)
- 方法三:查看 PHP-FPM 慢日志(针对偶发飙高)
- 方法四:PHP 内置 debug 函数(最直接,适合开发环境)
- 方法五:生产环境无侵入排查(xdebug + xhprof)
- 实战排查思路总结(按顺序尝试):
定位 PHP CPU 飙升的代码,通常遵循 “先即时抓取、再分析样本、最后定位代码” 的思路,以下是几种实战中最有效的方法:
使用 top + strace(最快定位“当前在干嘛”)
当 CPU 瞬间飙高时,马上执行:
-
找到进程 PID
top -c # 按 P(大写)按 CPU 排序,找到最耗 CPU 的 PHP 进程,PID
-
看它在执行什么系统调用(可选)
strace -p <PID> -c # 统计系统调用次数和耗时(可能产生大量日志,小心)
或者看实时的调用:
strace -p <PID> -e trace=execve,write,read,recvfrom,sendto
常见特征: 如果大量
recvfrom阻塞(等待网络/数据库),可能是 IO 慢导致 CPU 空转;如果全是用户态函数(无系统调用),基本是 PHP 逻辑死循环或密集计算。 -
查看 PHP 当前执行的文件和行号(最实用) 如果安装了
gdb:gdb -p <PID> -batch -ex "bt" 2>/dev/null | head -30
这能输出 PHP 进程的堆栈,但更推荐方法二(万能的火焰图)。
生成火焰图(终极武器)
火焰图能一次性看出 “哪个函数最耗 CPU / 在哪个文件哪一行”,是生产环境定位 CPU 问题的标准做法。
步骤:
-
安装 perf(Linux)
yum install perf -y # CentOS apt install linux-tools-common -y # Ubuntu
-
采集数据(几秒钟即可,生产环境安全)
# 采集 10 秒,采样频率 99Hz perf record -e cpu-clock -F 99 -g -p <PID> --sleep 10
-
生成火焰图
# 转换格式 perf script > out.perf # 用 FlameGraph 工具 git clone https://github.com/brendangregg/FlameGraph cd FlameGraph ./stackcollapse-perf.pl ../out.perf > out.folded ./flamegraph.pl out.folded > cpu.svg
然后浏览器打开
cpu.svg,看“平顶山”——最宽、最高的函数就是罪魁祸首。
查看 PHP-FPM 慢日志(针对偶发飙高)
如果问题不是持续稳定飙高,而是周期性或偶尔出现。
-
启用慢日志(生产环境建议常年开启)
; php-fpm.conf 或 pool.d/www.conf request_slowlog_timeout = 2s slowlog = /var/log/php-fpm/slow.log
-
查看慢日志
tail -f /var/log/php-fpm/slow.log
日志会显示:
[12-Feb-2025 10:00:01] [pool www] pid 12345 script_filename = /var/www/html/index.php [0x7f8b9c0b1000] *** sleeping function *** at /var/www/html/your_code.php:100但注意:慢日志只记录 超时 的请求,对瞬时死循环可能不够及时。
PHP 内置 debug 函数(最直接,适合开发环境)
在怀疑的代码段前后加时间戳,不适合生产但开发调试很方便:
$start = microtime(true);
// 可疑代码
for($i=0; $i<1000000; $i++) { /* 计算 */ }
$end = microtime(true);
error_log("可疑代码耗时: " . ($end - $start) . "秒");
生产环境无侵入排查(xdebug + xhprof)
- xhprof(推荐):轻量级性能分析工具,可以记录每个函数的调用次数和耗时。
- 安装:
pecl install xhprof
- 启用(自动触发):
// 在入口文件(如 index.php)开头 xhprof_enable(XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY); register_shutdown_function(function() { $data = xhprof_disable(); // 保存到文件或数据库,用 XHProf 图形界面分析 }); - 查看结果:图形界面会显示哪个函数占用了最多的 CPU 时间。
实战排查思路总结(按顺序尝试):
-
看进程堆栈:
sudo gdb -p <PID> -batch -ex "bt" 2>/dev/null | head -20
如果能看出是某个扩展函数(如curl_exec)、数据库查询(mysqli_query)或正则(preg_match)卡住,直接定位。 -
如果是死循环:马上执行
strace -p <PID>观察是否在不停地执行相同的系统调用(write刷日志、read轮询),如果是,看哪个文件被频繁打开(lsof -p <PID>查看异常的文件句柄)。 -
火焰图最万能:如果持续飙高,
perf record10 秒生成火焰图,一眼看出热点函数是“数据库查询”、“正则回溯”还是“循环计算”。 -
常见致命陷阱:
- 正则回溯:
preg_match配复杂模式,如/(a|aa)+b/对超长字符串匹配会导致灾难性回溯(CPU 100%)。 - 数据库慢查询:
sleep(1)在循环里执行 100 次,CPU 看似不高但进程阻塞;如果海量连接,CPU 会花在进程上下文切换上。 - 日志疯狂写入:没有
log_errors_max_len限制,特别是error_log($_POST)记录整个请求体。 - 无限递归:如某个方法调用自身没有退出条件。
- 正则回溯:
用
top -c找 PID,然后用perf采集生成火焰图(cpu.svg)看“平顶山”,99% 的 CPU 问题都能在火焰图上找到具体文件名和行号。