PHP-FPM性能调优实战:进程模式、max_children计算与慢日志定位


一、为什么 PHP-FPM 是建站性能的关键

LNMP 架构中,Nginx 负责接收请求与静态资源,PHP-FPM 负责把 PHP 脚本交给 PHP 解释器执行。PHP-FPM 的进程管理策略直接决定站点能扛多少并发、会不会 502、会不会内存被打满。默认配置往往"够用但不高明"——要么进程数过多吃满内存,要么过少导致排队 502。下面从进程管理模式、参数计算、慢日志定位三个层面,把 PHP-FPM 调优讲透。

二、先看懂 pm 三种模式

PHP-FPM 的进程池在 /etc/php/8.x/fpm/pool.d/www.conf(Debian/Ubuntu)或 /etc/php-fpm.d/www.conf(RHEL 系)中配置,核心是 pm 指令:

  • static:固定进程数,启动即建好,无伸缩开销,适合流量稳定、内存有富余的场景。
  • dynamic:按需在 min/max 之间增减进程,兼顾空闲与峰值,是大多数站点的默认与推荐。
  • ondemand:按请求才拉起进程,最省内存,但首次请求有冷启动延迟,适合低流量小站。
# dynamic 模式典型配置
pm = dynamic
pm.max_children = 20      # 最大进程数(同时处理请求上限)
pm.start_servers = 5      # 启动时进程数
pm.min_spare_servers = 3  # 空闲最少保持
pm.max_spare_servers = 8  # 空闲最多保持
pm.max_requests = 5000    # 每个进程处理 5000 次请求后回收,防内存泄漏累积

max_requests 非常实用:PHP 老代码常有内存泄漏,定期回收进程能避免单进程内存无限膨胀。设置过小会频繁重启进程反而增加开销,一般几千次为宜。

三、max_children 怎么算才不爆内存

核心公式:max_children ≈ 可用内存 ÷ 单进程平均内存。先看每个 PHP 进程实际吃多少内存:

# 当前 php-fpm 进程数与内存占用
ps -eo rss,cmd | grep php-fpm | grep -v grep | awk '{sum+=$1;n++} END {print "进程数:",n," 平均RSS(MB):",sum/n/1024}'

假设单进程平均 RSS 40MB、机器可用内存 2GB、系统与 Nginx/MySQL 预留 1GB,则:
max_children = (2-1)GB / 40MB ≈ 25。

设得过大:高并发下内存被 PHP 进程吃满,触发 OOM killer 连 MySQL 一起杀,表现为整机卡死;设得过小:请求排队,出现 502/504。调参要留足系统与数据库余量,尤其数据库连接数也随并发上涨,别把内存全部分给 PHP。

四、必开慢日志:让瓶颈无所遁形

站点变慢最怕瞎猜。PHP-FPM 的慢日志能精准记录"哪个脚本执行超过多少秒",是定位慢接口的利器:

# www.conf 中开启
slowlog = /var/log/php-fpm-slow.log
request_slowlog_timeout = 3s   # 超过 3 秒的执行计入慢日志
request_terminate_timeout = 60s # 单请求最长执行 60 秒,超时强制结束(防死循环拖垮进程)

查看慢日志:

tail -n 100 /var/log/php-fpm-slow.log
# 典型输出会给出脚本路径、请求开始时间,以及当时的 PHP 调用栈(哪一行卡住)

慢日志配合前面讲过的 Nginx access log 与 MySQL 慢查询,基本能锁定"是代码问题、SQL 问题还是配置问题"。注意 request_terminate_timeout 设置过小会误杀正常的长任务(如导出、批量处理),要按业务实际评估。

五、socket 与 listen 队列调优

PHP-FPM 默认监听 Unix socket,避免 TCP 开销,但 socket 的队列长度不足会丢请求导致 502。相关调优点:

# php-fpm www.conf
listen = /run/php/php8.2-fpm.sock
listen.backlog = 511      # 连接等待队列长度,大并发可调大到 1024
# listen.allowed_clients = 127.0.0.1  # TCP 模式时限制来源

# Nginx 侧 fastcgi 超时与缓冲(nginx.conf 中 location ~ \.php$)
fastcgi_connect_timeout 5s;
fastcgi_read_timeout 60s;   # 与 request_terminate_timeout 匹配
fastcgi_send_timeout 60s;

如果看到 "connect() to unix:/run/php/...sock failed (11: Resource temporarily unavailable)",多半就是 socket 队列被塞满——要么调大 backlog,要么提升 max_children,要么检查是否有慢脚本把进程占满了。

六、与 Nginx 和 MySQL 的联动调优

PHP-FPM 不是孤立的,链路任一环节都可能成为瓶颈:

# 观察 Nginx 上游与 PHP-FPM 连接状态
tail -f /var/log/nginx/error.log | grep -E 'upstream|connect|timed out'

# 关键联动检查项
1. Nginx worker 数是否足够(worker_processes auto)
2. keepalive 是否开启,避免频繁建连
3. MySQL max_connections 是否高于 PHP 并发请求数(否则 SQL 排队)
4. 是否配了 opcache(PHP 7+ 默认),并关注 opcache 内存是否够

opcache 是"免费的性能大礼包":它把编译后的字节码缓存下来,PHP 不再每次重复编译。检查与调优:

php -i | grep opcache.enable_cli
# opcache.enable=1 已开启,生产建议 opcache.memory_consumption=128M、validate_timestamps 视部署方式而定

七、常见故障速查表

502 Bad Gateway
  -> php-fpm 进程数打满 / socket 权限不对 / 进程崩溃,查 error.log 与 php-fpm 状态

504 Gateway Timeout
  -> fastcgi_read_timeout 太短,或脚本执行超时,调大超时并查慢日志

503 Service Unavailable
  -> 站点负载过高排队,或 php-fpm 未启动(systemctl status php8.2-fpm)

整机卡死/OOM
  -> max_children 过大吃满内存,按公式回调并加 Swap

快速看 php-fpm 实时状态:开启 pm.status_path = /status,配到 Nginx 后访问 https://site/status 能看 active 进程数、队列等指标,是判断"进程够不够"的直接依据。

写在最后

PHP-FPM 调优的要点可归纳为:选择合适 pm 模式、按内存公式算 max_children、开启慢日志定位真凶、调好 socket 与超时、联动 Nginx/MySQL/opcache 整体优化。切忌盲目抄网上"max_children=100"之类的配置,每台机器的内存与业务都不同,用状态页 + 慢日志 + ps 实测来定参数,才能既扛得住峰值、又不把内存吃爆。


手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

Git Webhook自动部署实战:post-receive钩子与一键回滚方案

Nginx缓存加速实战:fastcgi_cache与proxy_cache配置及缓存清理指南

评 论
更换验证码