一次 HTTP 请求在 PHP-FPM 里的一生
很多 PHP 项目的问题都出在对 FPM 工作方式的理解偏差上。把一次请求的完整生命周期过一遍,配置项的含义就都串起来了。
进程模型
FPM 是 master/worker 结构。master 负责管理进程池、监听 socket(TCP 或 unix socket)、按配置扩缩 worker;worker 真正执行 PHP 代码,一次处理一个请求。
关键点:worker 是同步阻塞的。一个 worker 同时只能服务一个请求,请求执行期间它占用着整个进程。所以并发能力 = worker 数量 × 平均每秒能处理的请求数。
pm 配置的取舍
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 6
pm.max_spare_servers = 14
pm.max_requests = 500
max_children决定内存上限,估算公式是可用内存 / 单个 worker 平均内存。设太大导致 OOM,设太小导致请求排队出现 502/504。max_requests让 worker 处理 N 个请求后重启,用来规避第三方扩展的内存泄漏。代价是重启瞬间损失并发能力。dynamic适合流量波动大的站点,static适合流量平稳且内存充足的服务。
请求处理流程
worker 从 accept 队列取请求,初始化执行环境(每次请求都是全新状态,包含超级全局变量、session 加载),执行脚本,把响应写给 Web 服务器,然后清理。
「每次请求都重新初始化」是 FPM 模型的本质,也是它简单可靠的原因:没有跨请求的全局状态,内存泄漏大多能在请求结束时被清理。
由此推出的排查思路
- 502:看 FPM 是否存活、socket 路径是否与 Web 服务器配置一致;
- 504:请求执行太慢,看
request_slowlog_timeout抓到的慢日志; - 请求排队:
listen.backlog打满或 worker 不足,看 FPM 状态页的listen queue。
# 状态页配置 pm.status_path = /fpm-status 后
curl -s localhost/fpm-status
与常驻进程的对比
Swoole、RoadRunner 这类常驻方案省掉了每次初始化,性能更高,但要求代码「全局状态安全」。FPM 用性能换简单,对绝大多数业务站点仍然是正确的选择——前提是配置合理。