本文目录导读:

定位 PHP CPU 占用高的问题,通常需要结合系统工具、PHP 自身以及代码逻辑三个层面进行排查,以下是系统的定位步骤:
第一层:快速定位到具体进程
首先找出是哪个 PHP 进程导致的问题。
-
top / htop(实时查看)- 执行
top,按P(大写 P)按 CPU 降序排列。 - 确认消耗 CPU 的进程是
php-fpm、php-cli(脚本)还是httpd(模组方式)。 - 记录下高负载的 PID (进程ID)。
- 注意:对于
php-fpm,如果多个 worker 很高,那是并发问题;如果只有 1 个极高,那是单个请求有问题。
- 执行
-
strace(跟踪系统调用,看它在干什么)- 对有问题的 PID 执行:
strace -cp <PID>(统计系统调用耗时) - 或者实时跟踪:
strace -T -p <PID>(-T显示每个调用的耗时) - 观察点:
- 是否大量调用
read/write? -> 磁盘 I/O 瓶颈。 - 是否大量
connect/sendto? -> 网络请求 或 数据库 慢。 - 是否非常多
nanosleep? -> 代码里有睡眠、轮询或锁等待。
- 是否大量调用
- 对有问题的 PID 执行:
第二层:深入 PHP 内部
既然确认了是 PHP 问题,继续看它在运行什么代码。
-
检查
php-fpm慢日志 (最高效的方法)- 修改
php-fpm.conf或www.conf配置:; 启用慢日志 request_slowlog_timeout = 5 ; 脚本执行超过5秒就记录 slowlog = /var/log/php-fpm/www-slow.log request_terminate_timeout = 60 ; 最多允许60秒,超时杀死进程
- 分析:重启 PHP-FPM 后,查看
www-slow.log,它会告诉你哪个 PHP 文件、哪一行函数卡住了,这是最直接的定位方式。
- 修改
-
使用
pmap查看内存映射 (排查死循环或内存泄漏)pmap -x <PID> | sort -k 3 -rn | head -10- 如果发现大量
[anon]段,或内存异常大,可能存在循环分配或大数组操作。
-
gdb抓取调用栈 (终极手段,适合无慢日志环境)- 安装
gdb并确保 PHP 安装了 debug 符号包(或带--enable-debug编译)。 - 执行:
gdb -p <PID> - 在
(gdb)提示符下输入:set pagination off thread apply all bt - 这会打印所有线程的堆栈。寻找
execute_ex或zend_vm_execute调用链,看它卡在哪个函数上。
- 安装
第三层:分析与解决常见原因
定位到具体文件后,根据现象分析原因:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 数据库查询慢 | 慢日志中显示大量 mysql_query / PDO->query |
开启数据库慢查询日志 为查询添加索引 使用 EXPLAIN 分析SQL |
| 大量 file_get_contents / curl | 请求外部 API 或读取文件无限阻塞 | 设置 curl 超时时间(如 CURLOPT_TIMEOUT)使用异步并发或队列处理 |
| 无限循环 | for(;;) 或 while(true) |
在循环内增加 break 条件或计数限制 |
| 正则表达式回溯 | 使用了类似 的复杂正则匹配长字符串 | 使用 preg_match 的 PREG_UNMATCHED_AS_NULL 或限制匹配长度,避免灾难性回溯 |
| session 文件锁阻塞 | 高并发下 session_start() 锁等待 |
高并发场景改用 Redis/Memcached 存储 session |
| OpCache 未开启 | 每次请求重复解析、编译 PHP 文件 | 开启 OpCache,设置合适的 opcache.validate_timestamps |
| OOM (内存不足) 导致频繁交换 | 物理内存耗尽,系统频繁 swap,I/O 飙升导致 CPU 软中断 | 排查内存泄漏,或增加物理内存 |
第四层:短期应急与常态监控
- 应急重启:
systemctl restart php-fpm或kill -USR2 <master pid>(平滑重启,不中断请求),但只是临时缓解,问题还会复现。 - 建立监控:
- 使用
top或 Prometheus + Node Exporter 监控 CPU。 - 部署 Pinpoint / SkyWalking 等 APM 工具,直接看到每个请求的调用链耗时。
- 无论是
php-fpm还是apache mod_php,慢查询日志都是首选方案,务必优先配置。
- 使用
总结命令清单
# 1. 找出最消耗 CPU 的 PHP 进程
ps aux --sort=-%cpu | grep php | head -20
# 2. 统计高 CPU 进程正在执行什么系统调用
strace -c -p <PID>
# 3. 查看 PHP 实时调用栈(如果支持)
gdb -batch -ex "thread apply all bt" -p <PID>
# 4. 检查正在执行的 PHP 脚本(PHP-FPM 环境下)
# 查看 /proc/<PID>/cmdline 或 PHP 状态页
cat /proc/$(ps aux | grep "php-fpm.*pool" | head -1 | awk '{print $2}')/cmdline
核心思路:先锁定进程(top) → 推断它在等什么(strace) → 定位具体代码(慢日志) → 修复逻辑,请先从开启慢日志开始。