本文目录导读:

排查 PHP 项目导致的服务器 CPU 过高问题,通常需要从系统层面、PHP 进程层面和应用代码层面逐层深入,以下是系统化的排查步骤和常用命令:
第一步:定位高 CPU 进程
确认是高 CPU 是哪个进程引起的。
- 使用
top或htop命令:top -c
- 按
P键(大写 P),让进程按 CPU 使用率排序。 - 观察占用 CPU 最高的进程是
php-fpm(或apache/nginx的 worker 进程)还是 MySQL、Redis 等其他服务。 - 常见结果 1: 全是
php-fpm进程。 → 问题出在 PHP 代码或 PHP-FPM 配置。 - 常见结果 2: MySQL 进程很高。 → 往往是 PHP 代码中的慢查询导致。
- 常见结果 3: Nginx/Apache 进程很高。 → 可能是静态资源问题或 DDOS 攻击。
- 按
第二步:分析 PHP-FPM 进程
如果确认是 php-fpm 导致 CPU 高,需要查看是哪个 PHP 请求在“吃”CPU。
1 使用 strace 追踪系统调用(适合快速确认瓶颈)
对高 CPU 的 PHP 进程 PID(假设为 12345)执行:
strace -p 12345 -c -S time 2>&1 # 运行几秒后按 Ctrl+C 看统计
- 如果看到大量
poll、select、epoll_wait→ 可能是在等待 I/O(数据库、Redis、文件)。 - 如果看到大量
clock_gettime、mmap→ 可能是在执行密集计算或循环。 - 如果看到大量
lstat、open、read→ 可能是频繁的file_exists()、is_file()或include/require操作。
2 使用 gdb 或简单查看调用栈(更精准)
# 查看 PHP 进程正在执行的代码(PHP 有调用栈)
gdb -p <PID> -batch -ex "bt" 2>/dev/null | head -20
# 或者更常用的方式,利用 PHP-FPM 的 pm.status_path 查看活跃请求
# 在 nginx 配置中添加:
# location ~ ^/php-fpm-status$ {
# include fastcgi_params;
# fastcgi_pass unix:/var/run/php-fpm.sock;
# fastcgi_param SCRIPT_FILENAME $fastcgi_script_name;
# }
# 访问 /php-fpm-status?full 查看正在执行的脚本
curl http://localhost/php-fpm-status?full
3 检查慢日志(最直接的方法)
CPU 高是间歇性的,慢日志是宝库。
- 开启 PHP-FPM 慢日志:
; php-fpm.conf 或 www.conf request_slowlog_timeout = 5 ; 超过 5 秒记录慢日志 slowlog = /var/log/php-fpm/slow.log request_terminate_timeout = 60 ; 超时 60 秒强制终止
- 分析慢日志:
tail -f /var/log/php-fpm/slow.log
- 重点看: 频繁出现的脚本路径 + 具体的函数调用堆栈(如
file_get_contents、curl_exec、preg_match在循环中)。
- 重点看: 频繁出现的脚本路径 + 具体的函数调用堆栈(如
第三步:分析应用代码(常见导致 CPU 高的 PHP 场景)
-
死循环或无限递归:
- 常见于
while(true)未正确跳出,或递归函数未写好终止条件。 - 排查: 查看慢日志中永远卡在某个函数内部。
- 常见于
-
低效的数据库查询(N+1 问题):
- 在循环内部执行 SQL 查询(如
foreach($users as $u) { $db->query(...) })。 - 排查: 开启 MySQL 慢查询日志,配合
pt-query-digest分析。
- 在循环内部执行 SQL 查询(如
-
频繁的文件操作:
file_exists()、is_file()每次读取 Session 或缓存。- 排查:
strace看到大量lstat/open调用。 - 解决: 使用
realpath_cache_size或改用 Redis/Memcached 存储 Session。
-
正则表达式回溯(ReDoS 攻击):
- 输入恶意字符串使得
preg_match()进入灾难性回溯。 - 排查: 查看慢日志,卡在
preg_match或preg_replace。 - 解决: 使用
preg_match('/pattern/', $str, $match, PREG_UNMATCHED_AS_NULL)或限制输入长度。
- 输入恶意字符串使得
-
大量计算请求(加密、图像处理):
- 如
openssl_encrypt、imagick操作大图、json_decode超大文件、base64_decode巨量数据。
- 如
-
僵尸进程或配置不当:
pm.max_children设置得太高,导致 PHP-FPM 进程数过多,CPU 抢占严重。pm.start_servers设置过大。
第四步:使用专业性能分析工具(进阶)
-
Xdebug + KCachegrind: 在开发环境或低流量服务器上,临时开启 Xdebug 的 profiler:
xdebug.mode = profile xdebug.output_dir = /tmp
生成
cachegrind.out.*文件,用 QCacheGrind 工具打开,可以看到哪个函数消耗 CPU 最多。 -
Tideways/XHProf(在线分析): 适用于生产环境,可以实时查看请求的 CPU 耗时、内存占用、函数调用次数。
-
New Relic / Datadog(APM 工具): 如果预算允许,这是最省力的方法,直接显示哪个 URL、哪个数据库查询最慢。
标准的排查路径
graph TD
A[服务器CPU告警] --> B{使用 top 看进程}
B --> C[MySQL进程高]
B --> D[其他进程高]
B --> E[php-fpm进程高]
C --> F[检查慢查询日志,优化SQL/加索引]
E --> G[开启PHP-FPM慢日志]
G --> H[检查慢日志中的脚本和堆栈]
H --> I[分析具体问题:死循环/N+1/正则/文件操作]
I --> J[修复代码/优化配置]
D --> K[检查是否为攻击/日志进程/备份进程]
最后提醒: 如果是周期性(如每小时的某分钟)CPU 瞬间飙高,优先排查计划任务(Cron Job),如果是持续高,优先排查前端 API 接口或爬虫。