Nginx 与 PHP-FPM 调优:先量清瓶颈,再确定进程数与超时
调优应从测量开始:用 Nginx 的 $request_time 与 $upstream_response_time 区分耗时发生在 PHP 侧还是别处;PHP-FPM 的并发上限由 pm.max_children 和内存预算共同决定,Nginx 侧的读超时需与 PHP 执行上限协调,否则会无效占用进程;所有具体数值都要在自有环境压测后确定,本文示例均未执行。
先给结论:Nginx 与 PHP-FPM 之间的调优,重点是根据实测结果决定两件事,而不在抄一份参数表——FPM 进程池能开多少并发,以及 Nginx 侧的超时相对 PHP 执行上限如何设置。在没有可核验压测记录的情况下,任何具体数字都只是占位;下文给出判断方法和示例位置,具体取值待核实。
先定位:慢在哪一段#
Nginx 通过 FastCGI 协议把请求转给 PHP-FPM,连接可以是 TCP(常见 127.0.0.1:9000),也可以是 Unix socket。请求总耗时可被拆成几段观察,最直接的做法是在 Nginx 日志里同时记录 $request_time(Nginx 视角的完整处理时间)与 $upstream_response_time(等待上游的时间):
- 两者都高、且差距不大:时间主要花在 PHP 及其下游(数据库、外部接口)。这时先查慢请求,不要急着加进程。
- upstream 时间高但 request_time 更高:除了 PHP,还要看响应体大小、客户端网络、传输环节。
- 出现 502/504:502 的常见原因之一是 FPM 进程池被占满或进程异常退出;504 常见于 Nginx 侧读超时先于 PHP 处理完成触发。这两类都只是常见解释,具体要结合 FPM 日志和进程状态确认。
PHP-FPM 自带的 slowlog(request_slowlog_timeout + slowlog)能给出慢请求的 PHP 调用栈,这是把“慢”落到具体代码位置的常用手段;status 页可用来观察活跃进程与排队情况,是否启用及路径以所用版本文档为准。
PHP-FPM:进程池就是并发上限#
FPM 同时能处理的请求数由 pm.max_children 决定,超出部分排队等待。因此这个值不能凭感觉给,而要按内存预算推:可用内存 ÷ 单进程峰值 RSS。峰值 RSS 必须在真实请求下观察,本文没有提供任何测量数据。
pm = static:进程数固定,无伸缩开销,适合负载稳定且内存可控的场景。pm = dynamic:按空闲进程数伸缩,需要同时设置pm.start_servers、pm.min_spare_servers、pm.max_spare_servers;三者关系不合法时 FPM 启动会报配置错误(具体报错文本以所用版本为准)。pm = ondemand:有请求才起进程,省内存但增加冷启动延迟。pm.max_requests:进程处理若干请求后重建,用于缓解长驻进程的内存增长,代价是周期性重新初始化。
Nginx:连接复用与超时协调#
两个容易忽略的点。其一,worker_processes auto 与 worker_connections 共同决定连接容量,但连接数不等于 FPM 并发数,前者更大通常无害,后者才是排队点。其二,超时设置要成对看:如果 Nginx 的 fastcgi_read_timeout 短于 PHP 侧的执行上限(max_execution_time、request_terminate_timeout),Nginx 已经放弃响应,PHP 进程仍会继续跑到自己的上限——这是资源被无效占用的常见放大路径,属于机制推断,需在自己的流量下验证。
连接复用方面,upstream 中配置 keepalive 并在 location 中开启 fastcgi_keep_conn,可以减少反复建连的开销;是否值得取决于请求频率与 FPM 侧监听方式,需实测对比。
示例配置(未执行)#
以下两段仅示意参数位置与相互关系,未在环境中执行,数值不可直接照搬。
# 未执行的示例
upstream php_fpm {
server unix:/run/php/php-fpm.sock;
keepalive 16;
}
server {
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass php_fpm;
fastcgi_keep_conn on;
fastcgi_read_timeout 30s; # 需与 PHP 侧执行上限协调
}
}
; 未执行的示例
[www]
pm = dynamic
pm.max_children = 32 ; 上限由内存预算 ÷ 单进程峰值 RSS 推出
pm.start_servers = 8
pm.min_spare_servers = 4
pm.max_spare_servers = 12
pm.max_requests = 500
request_terminate_timeout = 30s
slowlog = /var/log/php-fpm/slow.log
request_slowlog_timeout = 5s
改完先执行 php-fpm -t 做配置语法检查,再重载。
怎么证明改动有效#
- 一次只改一组参数,保留旧配置文件作为回滚点。
- 固定压测条件(同一脚本、同一并发梯度、同一数据量),记录改前改后的延迟分位、错误率、FPM 活跃进程与队列、CPU 与内存。
- 先在一台机器或一个 pool 上生效,确认无异常再推广。
缺少这组对照记录时,只能说“配置改了”,不能说“性能变好了”。
边界条件#
- 瓶颈在数据库或外部 API 时,调大
pm.max_children只是把排队位置前移,可能同时放大下游压力。 - 容器环境的 cgroup 内存上限必须和 FPM 进程数一起计算,否则 OOM 终止会被误判为 FPM 故障。
- 各参数的默认值与可选项随 PHP/Nginx 版本变化,本文未联网核验,请以所部署版本的官方文档与本地
php-fpm -t输出为准。