如果你去面试 PHP 中高级岗位,PHP-FPM 几乎是绕不开的话题。面试官不会只问你“PHP-FPM 是什么”,而是会顺着进程模型一路问到 max_children 怎么算、pm.max_requests 设多少、502 和 504 分别意味着什么。这篇文章就围绕这条主线,把面试高频考点和线上调优实战串起来讲。
一、先搞清楚 PHP-FPM 到底解决了什么问题
早期的 PHP 以 CGI 方式运行:每个请求都启动一个 PHP 进程,解析脚本、执行、退出。进程创建和销毁的开销巨大,并发一上来机器就扛不住。
FastCGI 是对 CGI 的改进,它让进程常驻,由主进程统一管理。PHP-FPM(FastCGI Process Manager)就是 PHP 官方实现的 FastCGI 进程管理器。它带来几个关键能力:
- 进程池管理,支持平滑重启
- 动态/静态/按需三种进程调度模式
- 慢日志、状态页、进程隔离
- 优雅重载配置,不中断服务
面试时可以一句话概括:PHP-FPM 是一个常驻的 FastCGI 进程管理器,Nginx 通过 FastCGI 协议把请求转发给它,它再把请求交给空闲的 worker 进程处理。
二、PHP-FPM 的进程模型
PHP-FPM 启动后会形成两类进程:
Master 进程(1 个)
负责读取配置、监听端口(或 Unix Socket)、管理 worker 进程池。它不处理任何请求,只做调度和信号处理。收到 reload 信号时会平滑重启 worker,收到 stop 时优雅退出。
Worker 进程(N 个)
真正处理 PHP 请求的进程。每个 worker 同一时刻只能处理一个请求,处理完才回到空闲状态等待下一个。这就是为什么 worker 数量直接决定了并发处理能力。
还有一类 Idle 进程,本质上是空闲的 worker,只是暂时没有请求可处理,Master 会根据配置决定是否回收它们。
理解这个模型后,很多问题就顺理成章了:
- 一个 worker 卡住,只影响它自己处理的那个请求,不会拖垮整个池——但如果大量 worker 都卡住,池子就被占满,新请求只能排队甚至被拒绝。
pm.max_children是 worker 数量的上限,超过这个数的请求会进入 listen backlog 队列等待。
三、三种进程管理模式
pm 参数决定 worker 的调度策略,面试必问。
static(静态)
启动时就创建固定数量的 worker,永不增减。优点是稳定、无进程创建开销;缺点是资源利用率低,低峰期也占着内存。适合流量平稳、内存充足的场景。
dynamic(动态)
根据负载动态调整 worker 数量,由三个参数控制:
pm.max_children:worker 数量上限pm.start_servers:启动时的 worker 数pm.min_spare_servers/pm.max_spare_servers:空闲 worker 的最小/最大值
这是最常用的模式。Master 会在空闲 worker 少于 min_spare_servers 时 fork 新进程,多于 max_spare_servers 时杀掉多余进程。
ondemand(按需)
平时几乎不保留 worker,有请求才创建,空闲超过 pm.process_idle_timeout 就回收。内存占用极低,但请求到来时有 fork 开销,适合流量稀疏的场景。
四、max_children 到底怎么算
这是调优的核心,也是面试最容易拉开差距的地方。很多人凭感觉设个 50、100,结果要么内存溢出,要么并发上不去。
正确思路是从内存倒推,而不是从 CPU 或 QPS 拍脑袋。
第一步,算出单个 PHP 进程的平均内存占用。可以用:
ps --no-headers -o rss -C php-fpm | awk '{sum+=$1} END {print sum/NR/1024 " MB"}'
或者看 top 里 php-fpm 进程的 RES 列。假设单进程约 60MB。
第二步,确定留给 PHP-FPM 的可用内存。比如一台 8GB 的机器,系统 + MySQL + Redis 占掉 4GB,剩 4GB 给 PHP-FPM。
第三步:
max_children = 可用内存 / 单进程内存
= 4096MB / 60MB ≈ 68
所以 pm.max_children 设 60~68 比较稳妥,留一点余量避免 OOM。
这里有个常见误区:max_children 不是越大越好。设得超过内存承受能力,一旦流量高峰所有 worker 都被拉起,直接触发 OOM Killer,进程被系统杀掉,表现为大量 502,比排队还糟。设得太小,则请求堆积,响应变慢甚至超时。
CPU 核数更多影响的是 worker 的“有效并发”,因为进程切换和上下文切换有成本。一般来说 worker 数可以是 CPU 核数的几倍到十几倍,因为 PHP 大部分时间在等 IO(数据库、缓存、网络),不是纯计算。但最终仍以内存为准。
五、其他关键参数
pm.max_requests
每个 worker 处理多少个请求后自动重启。设为 500~1000 比较常见。它的作用是防止内存泄漏累积——PHP 扩展、第三方库难免有泄漏,定期重启进程能回收内存。但设得太小会导致频繁 fork,增加开销。
request_terminate_timeout
单个请求的最大执行时间,超时后 worker 被 kill。要大于 max_execution_time,否则 PHP 还没报错进程就没了。
listen.backlog
等待队列长度,默认 511。高并发下可适当调大,配合 Nginx 的 fastcgi_connect_timeout 一起看。
slowlog
开启慢日志能定位到具体哪个脚本、哪个请求拖慢了 worker,是排查性能问题的利器。
六、502 和 504 的排查思路
面试常问“线上出现 502 怎么查”,可以这样回答:
502 Bad Gateway 通常是 Nginx 连不上 PHP-FPM 或 worker 不够用:
- PHP-FPM 进程挂了或没启动
max_children太小,请求被拒- backlog 队列满了
- Socket 文件权限或路径不对
排查:看 php-fpm.log 有没有 server reached pm.max_children 警告,看 netstat 的 listen 队列溢出统计。
504 Gateway Timeout 是 Nginx 等 PHP-FPM 响应超时:
- 脚本执行太久(慢 SQL、外部接口卡住)
- worker 全被慢请求占满,新请求排队超时
request_terminate_timeout和 Nginx 超时配置不匹配
排查:开 slowlog 定位慢脚本,看 max_children 是否被打满。
七、调优实战小结
把上面的内容浓缩成一套可落地的流程:
- 用
ps测出单进程内存,按可用内存算出max_children上限 - 流量平稳选 static,波动大选 dynamic,稀疏选 ondemand
- dynamic 模式下
start_servers取max_children的 10%~20%,min/max_spare_servers围绕它设置 max_requests设 500~1000 防内存泄漏- 开 slowlog 和 status 页,持续观察
- 出现 502/504 先看 FPM 日志和
max_children是否被打满
最后提醒一句:任何参数都不要照搬网上的“最佳实践”。同样的 max_children=100,在 2GB 机器上会 OOM,在 32GB 机器上可能绰绰有余。调优的本质是结合自己机器的内存、流量特征和业务类型去算、去压测、去观察。面试时能把“为什么这么算”讲清楚,比背出一个数字有价值得多。
未经允许不得转载:任鹏个人博客 » PHP 面试题:PHP-FPM 的进程模型与 max_children 调优实战

