PHP 怎么排查CPU高

wen PHP项目 8

PHP 应用 CPU 飙高?从应急到根治的全链路排查指南(附实战命令)**

PHP 怎么排查CPU高


目录导读

  1. 现象确认:别把“假高”当“真高”
  2. 第一板斧:定位是哪个 PHP 进程在“烧”CPU
  3. 第二板斧:用 strace 追踪系统调用,揪出“代码黑洞”
  4. 第三板斧:开启 PHP-FPM 慢日志与 xdebug 性能剖析
  5. 深挖代码层:算法、循环、死锁与外部调用
  6. 进阶排查:MySQL 慢查询与 Redis 阻塞的联动分析
  7. 长期防护:从监控告警到容量规划
  8. 高频问答(FAQ)

当你的 PHP 服务器 CPU 使用率突然冲到 90% 以上,页面响应变得龟速,甚至出现 502 Bad Gateway 时,别慌,绝大多数情况下,这是一个“可复现、可定位、可修复”的常规故障,本文结合 Linux 系统工具与 PHP 运行时特性,为你提供一套从应急到根治的完整排查思路。

现象确认:别把“假高”当“真高”

我们要区分是持续高还是瞬时尖峰,执行 top -H -p <php-fpm主进程PID> 观察多核负载分布,如果只有单个线程 CPU 占用 100%,而其他核心空闲,这通常是某个请求内发生了死循环或超大数组遍历;如果所有 worker 进程 CPU 都高,那可能是并发压力过大,也可能是共用资源(如 Memcached)出现瓶颈。

关键命令uptime 查看 load average,vmstat 1 观察 r(运行队列)和 us(用户态 CPU)列。us 高而 sy(系统态)低,说明是纯 PHP 计算问题;sy 也高,则要考虑系统调用频繁或上下文切换过多。

第一板斧:定位是哪个 PHP 进程在“烧”CPU

使用 top 按 P 键按 CPU 排序,记下 PID,随后用 ps -ef | grep PID 确认它对应的是哪个 PHP-FPM worker 池,更重要的是,我们需要拿到这个进程正在执行的 PHP 脚本。

实战技巧:使用 pm.status_path(在 php-fpm.conf 中开启 pm.status_path = /status),配合 curl 命令访问,可以看到当前活跃请求的脚本路径,示例:

curl http://127.0.0.1/status?full | grep -A 5 "pid: 12345"

该输出会给出脚本文件名、请求时长、内存占用,能快速锁定“肇事脚本”。

第二板斧:用 strace 追踪系统调用,揪出“代码黑洞”

top 显示该进程 CPU 高,但 pm.status_path 看不到具体脚本(比如正在执行外部命令或网络阻塞),我们可以用 strace 直接观察:

strace -p <PID> -c -f -o /tmp/trace.log

等待 5-10 秒后 Ctrl+C 结束。strace-c 参数会汇总系统调用次数和耗时,重点查看 readwritepollselect 等调用。

典型场景:如果发现大量的 futexsched_yield 调用,说明进程在锁竞争或空转(死循环);pollepoll_wait 超时时间极长,则可能是阻塞于外部 HTTP 请求或数据库连接。你大概率是在循环里写了 file_get_contentscurl,且没有设置超时时间

第三板斧:开启 PHP-FPM 慢日志与 xdebug 性能剖析

慢日志是最直接的“破案工具”,在 php-fpm.conf 中设置:

request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm-slow.log

当脚本执行超过 5 秒时,慢日志会打印调用栈(backtrace),直接告诉你卡在哪个函数,这是定位“效率低但非死循环”问题的利器。

若慢日志未捕获,但 CPU 高,则用 xdebug 做一次即时剖析:

php -d xdebug.mode=profile -d xdebug.output_dir=/tmp/prof /path/to/script.php

生成的 cachegrind 文件用 qcachegrind(Linux)或 Webgrind 打开,能看到每个函数的调用次数和自耗时间。注意:生产环境勿持续开启,建议针对引起问题的具体请求做临时捕获。

深挖代码层:算法、循环、外部调用与单例误用

排查完性能工具,最终要回到代码本身,以下是 90% 的高 CPU 根因:

  • 无意识的大数组遍历:在 foreach 中嵌套 array_search,时间复杂度为 O(n²),改为 isset + 哈希键。
  • 正则表达式灾难preg_match 包含大回溯量,如 (a+)+$ 对长字符串匹配会引发指数级回溯,使用 preg_match 后检查 PREG_BACKTRACK_LIMIT_ERROR
  • 进程内缓存失效连环:每次请求都重新 require 一个 5MB 的大配置数组,且未用 OPCache。务必确认已开启 opcache.enable=1opcache.revalidate_freq=60
  • 外部调用无超时:Guzzle 或 curl 默认永不超时,当第三方 API 响应慢时,PHP 进程会一直等待,CPU 显示为“高”是因为进程未释放,实际是 IO 等待,但负载会攀升。
  • 单例模式误用:在一个 Worker 进程中,如果通过静态变量保存了某个巨大对象,且该对象包含一次性计算,后续请求可能反复触发重计算。

进阶排查:MySQL 慢查询与 Redis 阻塞的联动分析

CPU 高不一定是 PHP 本身,MySQL 的 SELECT 导致锁等待,PHP 进程会阻塞占满连接数,表现为高 CPU(其实是空闲等待),执行:

SHOW PROCESSLIST;

查看是否有大量 Copying to tmp tableSending data 状态的线程,同时查看 slow_query_log,找出耗时超过 2 秒的 SQL。滥用 ORDER BY RAND() 或者在 WHERE 条件中使用了非索引列是主因。

同样,检查 Redis:redis-cli --latencyslowlog get,Redis 执行了 KEYS * 或超大 SMEMBERS,会阻塞 PHP 进程导致 CPU 尖峰。

长期防护:从监控告警到容量规划

排查完成后,建议搭建以下防线:

  • 日志采集:ELK 或 Loki 集中分析 Nginx 访问日志与 PHP-FPM 慢日志。
  • 实时告警:Prometheus + Alertmanager 监控 CPU 使用率,阈值 80% 持续 10 分钟触发。
  • 熔断机制:在代码层为外部 API 调用设置超时(CURLOPT_TIMEOUT_MS),并以服务降级替代无限阻塞。
  • 定期压测:使用 Apache Bench 或 wrk 模拟全链路压测,预先发现瓶颈。

高频问答(FAQ)

问:CPU 高一定代表 PHP 代码有问题吗? 答:不一定,可能是磁盘 I/O 慢(iowait 高),或网络流量突发,务必结合 vmstatmpstat 确认。

问:strace 看到大量 read(0, 调用是什么意思? 答:这通常表示进程在读取标准输入,如果它是 PHP-FPM worker,可能是在等待 Nginx 传递请求体;若 read 上没有数据返回,说明客户端断连但服务端未感知。

问:OPCache 已经开启,CPU 仍高,为什么? 答:OPCache 只解决“编译”开销,若代码本身有 10 万次循环,那 CPU 依赖算法效率,建议用 Xdebug 剖析后优化算法,或者引入队列削峰填谷。

问:线上环境不敢开慢日志和 strace,怎么办? 答:可通过 PHP-FPM 管理命令配合 kill -SIGUSR2 刷新配置,或仅对一个 worker 附加慢日志,也可以采用 php-fpm -p 复制一个临时实例,将线下请求复制到该实例上测试。


通过以上六步,你不仅能解决当下的 CPU 飙高,还能建立一套常态化的性能排障机制,排查的核心是 “先看全局,再看进程,再看调用栈,最后看业务代码”,切忌盲目猜测,愿你以后遇到此类问题,都能从容应对。

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