用户登录

技术文章

外部链接将在新标签页打开。

Nginx 与 PHP-FPM 调优:先建容量模型,再改参数

一句话回答

Nginx 与 PHP-FPM 调优的起点不是参数表,而是判断请求在哪一层排队。本文说明连接数与进程池各自的容量约束、FastCGI 连接复用与缓冲的作用,并给出一个有先后顺序的改动清单;所有配置仅为示意,未在真实环境执行。

核心要点 TL;DR
  • 调参前先分层定位:连接层、进程池、PHP 执行、下游依赖,四类瓶颈的处置方式不同。
  • PHP-FPM 的 pm.max_children 受内存约束,不是越大越好;Nginx 的 worker_connec
  • 开启 FastCGI 长连接与 slowlog 的收益通常先于调大缓冲区。
  • 本文配置为示意,未执行,默认值与版本行为请以所用版本文档为准。

Nginx 与 PHP-FPM 的性能问题,多数不是"某个参数没调对",而是两层之间的容量模型没有对齐:Nginx 按连接数接纳请求,PHP-FPM 按进程数执行请求,两边额度不匹配时,排队就会从一个位置挪到另一个位置。所以调优的起点是定位瓶颈层,而不是抄一份参数表。

下面涉及参数语义的部分属于通用说明;具体默认值、版本差异与上限行为,请以所用 Nginx 与 PHP-FPM 版本文档为准,本次未联网核验。

先判断请求停在哪一层#

同一组现象可能对应完全不同的原因,先分层再动手:

  • 连接层:Nginx 无法接纳更多连接,表现为新连接被拒或握手变慢。关注 worker 进程数与每个 worker 的连接额度。
  • 进程池层:请求已到 PHP-FPM,但没有空闲进程执行,表现为请求在队列里等待、耗时整体被拉长。
  • PHP 执行层:进程拿到了,但单个请求执行很久。看慢日志与分段耗时。
  • 下游依赖:数据库、缓存、外部接口慢。这一层无论怎么调 Nginx 都不会变快。

区分方法是看耗时分布的形状:整体等比例变长更像排队,少数请求极慢更像执行层或依赖层(这是推断性的判断方法,不是实测结论)。

Nginx 侧:连接额度要留出上游份额#

worker_connections 是单个 worker 进程可同时打开的最大连接数。关键点在于:与上游(这里是 PHP-FPM)的连接通常也占用这个额度。若请求路径是"客户端 → Nginx → PHP-FPM",一个正在处理的请求会同时占用一条客户端连接和一条上游连接。只按客户端数量估算额度,会在压力上来时提前耗尽。

因此 worker_processes 与 worker_connections 要一起看,并与 PHP-FPM 能同时处理的请求数大致对齐。上游侧超出 PHP-FPM 处理能力的部分,留在 Nginx 里排队并不会更快。

FastCGI 连接复用

默认情况下 Nginx 与 PHP-FPM 之间往往是短连接,每个请求都要建立一次连接。若使用 TCP,这会带来握手开销;使用 Unix socket 时开销较小,但仍非零。在 FPM 侧开启连接保持、并在 Nginx 侧对应配置后,连接可以复用:

# 示意配置,未执行;参数名与可用性请对照所用版本文档
upstream php_fpm {
    server unix:/run/php-fpm.sock;
    keepalive 32;          # 保留的空闲上游连接数
}

location ~ \.php$ {
    fastcgi_keep_conn on;
    fastcgi_pass php_fpm;
    include fastcgi_params;
}

注意 keepalive 的值是保留的空闲连接数,不等于并发上限;设得比实际并发大很多并不会带来额外收益。

缓冲与响应体积

fastcgi_buffer_size、fastcgi_buffers 决定响应头与响应体的缓冲能力。响应体超过缓冲总额时会被写入临时文件,这在返回大 JSON 或文件时可能明显增加磁盘 I/O。判断依据是响应体积的实际分布,而不是凭感觉调大——把缓冲调到远超实际响应大小,只会增加每个连接的内存占用。

PHP-FPM 侧:进程池是一个内存预算问题#

PHP-FPM 的进程池有三种调度模式:static 固定进程数、dynamic 按空闲情况伸缩、ondemand 按需创建。pm.max_children 是上限,也是最容易被盲目调大的参数。

它的真实约束来自内存:每个 PHP 进程常驻内存乘以进程数,是这台机器必须付得起的账。可用的估算方法是:用单进程常驻内存(可从进程监控或 FPM 状态页获得)乘以预期的 max_children,再加上 Nginx、系统缓存与其他服务的占用,与可用内存比较。这是估算方法,具体数值需在目标环境测量,本文不提供任何实测数字。

; 示意配置,未执行
[www]
pm = dynamic
pm.max_children = 40        ; 由内存预算反推,而非照抄
pm.start_servers = 8
pm.min_spare_servers = 4
pm.max_spare_servers = 12
pm.max_requests = 500       ; 让进程周期性退出,回收缓慢增长的内存
request_terminate_timeout = 30s
request_slowlog_timeout = 2s
slowlog = /var/log/php-fpm/slow.log

几个容易忽略的约束:

  • pm.start_servers、pm.min_spare_servers、pm.max_spare_servers 必须落在 max_children 之内,否则配置不生效或无法启动。
  • pm.max_requests 是缓解内存缓慢增长的常用手段,代价是进程重建;设为 0 表示不回收。
  • 慢日志比任何参数猜测都更有信息量:它直接给出哪些请求慢、慢在调用的哪一段。
  • request_terminate_timeout 与 Nginx 的 FastCGI 超时要放在一起看,两者的先后顺序决定客户端最终看到的是超时还是 502。

参数之间的约束关系#

参数作用与谁相互约束
worker_connections单 worker 的连接上限客户端连接 + 上游连接的总和
keepalive(upstream)空闲上游连接保留数实际并发量与 FPM 空闲进程数
pm.max_childrenFPM 并发执行上限可用内存、Nginx 侧排队策略
fastcgi_buffers响应体缓冲实际响应体积分布

这张表想说明的是:这些参数不是各自独立的最优值,改一个就会改变另一个的实际含义。

一个有顺序的改动清单#

  1. 先开慢日志与 FPM 状态页,拿到耗时分布与进程池占用情况。
  2. 确认瓶颈层:连接、进程池、执行还是依赖。
  3. 若在进程池,按内存预算定 max_children,再让 min/max spare 落在其内。
  4. 若连接层紧张,先核对 worker 数与连接额度是否为上游留了份额,再考虑调大。
  5. 开启 FastCGI 长连接,观察是否减少了连接建立开销。
  6. 最后才调整缓冲区,且以实际响应体积分布为依据。

边界与不确定性#

本文给出的是一套判断顺序与参数之间的约束关系,不构成任何环境下的推荐数值。默认值、参数是否仍受支持、以及长连接在特定 FPM 配置下的行为,都随版本变化,需以实际部署版本的文档与目标环境测量为准。此外,当瓶颈位于应用代码或下游依赖时,调整 Nginx 与 PHP-FPM 参数通常只能改变排队位置,不会改变总处理能力。

常见问题(FAQ)

把 pm.max_children 调大能解决 502 吗?
不一定。502 表示 Nginx 拿不到有效的上游响应,可能是进程池打满、进程崩溃、超时或 socket 权限问题。进程池打满时调大只是延后触发,同时增加内存压力;如果不是进程池瓶颈,调大不会有改善。
Nginx 参数调优能解决 PHP 慢吗?
不能。Nginx 侧参数影响的是连接接纳、缓冲与上游连接复用,PHP 执行耗时由应用代码与下游依赖决定。这类问题要看 PHP-FPM 慢日志与应用侧耗时分布。

作者:沐小悠

本文约 8 分钟读完 · 关键词:Nginx、PHP-FPM、pm.max_children、worker_connections、FastCGI

更多文章 GET 示例接口

「」标签下的其他文章