运维知识
悠悠
2025年9月25日

线上服务突然卡死?教你几招快速定位PHP-FPM进程假死问题

前几天晚上11点多,正准备洗洗睡了,突然收到客户信息服务502了。用户投诉页面打不开。这种时候真的是心态爆炸,赶紧爬起来排查问题。经过一番折腾,发现是PHP-FPM进程假死导致的。今天就跟大家分享一下,遇到这种进程假死问题该怎么快速定位和解决。

什么是进程假死

进程假死其实就是进程还在,但是不干活了。你用ps命令看,进程确实存在,但就是不处理请求。就像人还坐在工位上,但是已经神游天外了,叫都叫不醒那种感觉。

PHP-FPM作为FastCGI进程管理器,负责管理PHP进程池。当它出现假死时,表现就是:

  • 进程存在但不响应新请求
  • CPU使用率可能很低或者异常高
  • 内存占用可能持续增长
  • 日志可能停止更新或者出现异常

快速判断是否为进程假死

遇到服务异常,我一般会按这个顺序来排查:

看进程状态

ps aux | grep php-fpm

正常情况下,你会看到类似这样的输出:

root      1234  0.0  0.1 123456  7890 ?        Ss   10:00   0:00 php-fpm: master process
www       1235  0.0  0.2 123456  8901 ?        S    10:00   0:00 php-fpm: pool www
www       1236  0.0  0.2 123456  8902 ?        S    10:00   0:00 php-fpm: pool www

如果看到进程状态是D(不可中断睡眠)或者Z(僵尸进程),那基本就是有问题了。

image-20250925221751266

  • STAT: 进程状态码

    • S: 睡眠状态
    • s: 会话领导者
    • l: 多线程
    • +: 前台进程组
    • R: 运行中
    • D 状态的进程通常是在等待 I/O 操作完成,如磁盘读写
    • Z 状态 (僵尸进程),僵尸进程是已经终止但父进程尚未调用 wait() 获取其退出状态的进程

检查进程响应

# 查看PHP-FPM状态页面(需要先配置)
curl http://localhost/status

# 或者直接测试PHP页面响应
curl -w "@curl-format.txt" -o /dev/null -s "http://your-site.com/test.php"

如果curl一直卡住不返回,或者返回时间特别长,那就很可能是假死了。

观察系统资源

# 查看CPU使用情况
top -p `pgrep php-fpm | tr '\n' ',' | sed 's/,$//'`

# 查看内存使用
free -h

# 查看磁盘IO
iostat -x 1

image-20250925222118662

我遇到过好几次,PHP-FPM假死时CPU使用率接近100%,但实际上什么都没在处理。

深入分析假死原因

使用strace追踪系统调用

strace是个神器,可以实时查看进程在做什么系统调用:

# 找到问题进程PID
ps aux | grep php-fpm | grep -v master

# 追踪系统调用
strace -p 进程PID -f -e trace=all

我之前遇到过一次,strace显示进程一直在执行connect()系统调用,原来是连接MySQL时网络有问题,导致进程一直卡在连接上。

查看进程调用栈

有时候strace信息太多看不过来,可以用gdb查看调用栈:

gdb -p 进程PID
(gdb) bt
(gdb) info threads
(gdb) thread apply all bt

不过说实话,gdb对于生产环境来说有点重,一般我只在测试环境用。

image-20250925230430011

分析PHP-FPM慢日志

PHP-FPM有个很有用的功能就是慢日志,可以记录执行时间超过阈值的请求:

; 在php-fpm.conf中配置
slowlog = /var/log/php-fpm/slow.log
request_slowlog_timeout = 5s

慢日志会记录详细的调用栈,比如:

[26-Oct-2023 15:30:45]  [pool www] pid 12345
script_filename = /var/www/html/index.php
[0x00007f8b8c0c8000] curl_exec() /var/www/html/api.php:45
[0x00007f8b8c0c8100] api_call() /var/www/html/index.php:23

通过慢日志,你能很快定位到是哪个函数卡住了。

常见的假死场景和解决方案

数据库连接问题

这个真的太常见了。数据库连接池满了,或者网络抖动,都可能导致PHP进程卡在数据库操作上。

解决方案:

  • 设置合理的数据库连接超时时间
  • 使用连接池,避免频繁建立连接
  • 监控数据库连接数
// 设置MySQL连接超时
$pdo = new PDO($dsn, $user, $pass, [
    PDO::ATTR_TIMEOUT => 5,
    PDO::MYSQL_ATTR_INIT_COMMAND => "SET SESSION wait_timeout=30"
]);

外部API调用超时

调用第三方API时没设置超时,对方服务挂了你也跟着挂。我见过太多这种情况了,一个支付接口的问题导致整个网站瘫痪。

// 使用curl时一定要设置超时
$ch = curl_init();
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);

文件锁竞争

多个进程同时操作同一个文件,可能导致死锁:

// 使用flock时要注意超时
$fp = fopen('data.txt', 'w');
if (flock($fp, LOCK_EX | LOCK_NB)) {
    // 获得锁,执行操作
    fwrite($fp, $data);
    flock($fp, LOCK_UN);
} else {
    // 获取锁失败,记录日志或者返回错误
    error_log('Failed to acquire file lock');
}
fclose($fp);

磁盘IO问题导致的假死

这个问题特别隐蔽,经常被忽略。磁盘IO性能差或者磁盘故障,会导致进程卡在文件读写操作上。

快速检测磁盘IO问题

# 查看磁盘IO使用率
iostat -x 1 5

# 重点关注这几个指标:
# %util - 磁盘使用率,接近100%说明磁盘很忙
# await - 平均等待时间,超过20ms就要注意了
# svctm - 平均服务时间

我遇到过一次特别坑的情况,服务器磁盘的%util一直在99%以上,但是通过top看CPU使用率很低。后来发现是磁盘坏道导致的,读写特别慢。

找出占用IO的进程

# 安装iotop工具
apt install iotop -y

# 实时查看IO使用情况
iotop -o -d 1

# 或者使用pidstat
pidstat -d 1

iotop的输出类似这样:

Total DISK READ :       0.00 B/s | Total DISK WRITE :      12.34 M/s
Actual DISK READ:       0.00 B/s | Actual DISK WRITE:      15.67 M/s
  TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND
 1234 be/4  www         0.00 B/s   10.23 M/s  0.00 %  85.67 % php-fpm: pool www

如果看到某个PHP-FPM进程的IO使用率特别高,那就要重点关注了。

分析具体的文件操作

# 使用lsof查看进程打开的文件
lsof -p 进程PID

# 或者查看进程的文件描述符
ls -la /proc/进程PID/fd/

我之前遇到过一个案例,PHP程序在写日志时没有正确关闭文件句柄,导致同一个日志文件被打开了几千次,磁盘IO直接爆炸。

磁盘空间不足的问题

# 检查磁盘使用情况
df -h

# 查找大文件
find /var/log -type f -size +100M -exec ls -lh {} \;

# 查看目录大小
du -sh /var/log/*

磁盘空间不足时,写操作会变得特别慢,甚至失败。PHP-FPM进程可能会卡在日志写入或者临时文件创建上。

内存泄漏导致的假死

PHP进程内存使用过多,触发系统的OOM机制,进程就卡住了。

可以通过以下方式监控:

# 查看进程内存使用
cat /proc/进程PID/status | grep VmRSS

# 或者用ps
ps -o pid,vsz,rss,comm -p 进程PID

应急处理方案

重启PHP-FPM服务

最简单粗暴的方法:

# CentOS/RHEL
systemctl restart php-fpm

# Ubuntu/Debian  
systemctl restart php7.4-fpm

# 或者直接kill掉重启
pkill php-fpm
/usr/sbin/php-fpm -D

不过重启会中断正在处理的请求,生产环境要慎重。

平滑重启

PHP-FPM支持平滑重启,不会中断现有连接:

# 发送USR2信号进行平滑重启
kill -USR2 `cat /var/run/php-fpm.pid`

# 或者使用systemctl
systemctl reload php-fpm

预防措施

监控监控监控!!!(重要的事情说三遍)

日志记录

开启详细的日志记录,方便问题排查:

; php-fpm.conf
log_level = notice
access.log = /var/log/php-fpm/access.log
access.format = "%R - %u %t \"%m %r\" %s %f %{mili}d %{kilo}M %C%%"

定期重启

对于一些老项目,可能存在内存泄漏问题,可以设置定期重启:

# 添加到crontab,每天凌晨2点重启
0 2 * * * /usr/bin/systemctl reload php-fpm

一些踩过的坑

不要随便kill -9

很多人遇到进程假死第一反应就是kill -9,但这样可能会导致数据不一致。最好先尝试kill -TERM让进程优雅退出。

注意PHP-FPM版本差异

不同版本的PHP-FPM配置参数可能不一样,升级时要注意兼容性。我就遇到过从PHP 7.2升级到7.4后,原来的配置不生效的情况。

监控指标要合理

设置监控阈值时不要太敏感,否则会产生很多误报。我之前设置响应时间超过1秒就告警,结果每天收到几十条告警消息,后来调整到5秒才比较合理。

磁盘IO监控容易被忽略

很多人只关注CPU和内存,忽略了磁盘IO。其实磁盘IO问题导致的服务假死非常常见,特别是那些有大量文件操作的应用。

我建议在监控系统中加入这些磁盘相关的指标:

  • 磁盘使用率(%util)
  • 平均等待时间(await)
  • 磁盘空间使用率
  • inode使用率

日志轮转很重要

PHP-FPM的日志文件会越来越大,一定要配置logrotate进行日志轮转,否则磁盘满了又是另一个问题。

# /etc/logrotate.d/php-fpm
/var/log/php-fpm/*.log {
    daily
    missingok
    rotate 7
    compress
    delaycompress
    notifempty
    postrotate
        /bin/kill -USR1 `cat /var/run/php-fpm.pid 2>/dev/null` 2>/dev/null || true
    endscript
}

临时文件清理

PHP会在/tmp目录下创建临时文件,如果程序异常退出,这些临时文件可能不会被清理。时间长了会占用大量磁盘空间和inode。

# 定期清理PHP临时文件
find /tmp -name "php*" -type f -mtime +1 -delete

# 清理session文件
find /var/lib/php/session -name "sess_*" -type f -mtime +1 -delete

可以把这些命令加到crontab里定期执行。

高级排查技巧

使用perf分析性能

对于复杂的性能问题,可以使用perf工具进行深入分析:

# 安装perf工具
yum install perf -y

# 对指定进程进行采样
perf record -p 进程PID -g -- sleep 30

# 查看报告
perf report

perf可以告诉你进程把时间都花在哪里了,对于定位性能瓶颈很有帮助。

使用systemtap进行动态追踪

systemtap是个更强大的工具,可以动态插入探针:

# 监控文件IO操作
stap -e 'probe syscall.read, syscall.write { 
    if (pid() == target()) 
        printf("%s: %s\n", name, argstr) 
}' -x 进程PID

不过systemtap比较复杂,一般情况下用strace就够了。

分析core dump文件

如果进程崩溃了,可以通过core dump文件分析崩溃原因:

# 启用core dump
ulimit -c unlimited
echo '/tmp/core.%e.%p' > /proc/sys/kernel/core_pattern

# 使用gdb分析core文件
gdb /usr/sbin/php-fpm /tmp/core.php-fpm.12345
(gdb) bt
(gdb) info registers

写在最后

进程假死问题虽然棘手,但只要掌握了正确的排查方法,还是能够快速定位和解决的。关键是要:

  1. 建立完善的监控体系,及时发现问题
  2. 熟练掌握各种排查工具的使用
  3. 针对常见场景做好预防措施
  4. 特别要重视磁盘IO问题,这个经常被忽略但影响很大
  5. 保持冷静,按照既定流程逐步排查

记住,运维工作很多时候就是在和各种奇奇怪怪的问题做斗争。每解决一个问题,都是经验的积累。下次遇到类似问题时,你就能更快地定位和解决了。

磁盘IO问题特别值得重视,因为它往往比较隐蔽,不像CPU或内存问题那么明显。很多时候系统看起来资源充足,但就是响应慢,这时候就要想到是不是磁盘IO的问题了。

当然,最重要的还是要在平时做好预防工作。合理的配置、完善的监控、定期的维护,能够避免大部分的线上问题。毕竟,预防永远比治疗更重要。

希望这篇文章能帮到正在被进程假死问题困扰的朋友们。如果你有其他好的排查方法或者遇到过有趣的案例,欢迎在评论区分享交流。


如果这篇文章对你有帮助,别忘了点赞转发支持一下!想了解更多运维实战经验和技术干货,记得关注微信公众号@运维躬行录,领取学习大礼包!!!我会持续分享更多接地气的运维知识和踩坑经验。让我们一起在运维这条路上互相学习,共同进步!

公众号:运维躬行录

个人博客:躬行笔记

文章目录

博主介绍

热爱技术的云计算运维工程师,Python全栈工程师,分享开发经验与生活感悟。
欢迎关注我的微信公众号@运维躬行录,领取海量学习资料

微信二维码