用户登录

技术文章

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

Nginx 与 PHP-FPM 性能调优实战:没有基线就没有参数

一句话回答

性能调优的可行顺序是先量化瓶颈在哪一层,再每次只改一个参数并复测;Nginx 侧重点关注 worker 进程与连接上限、上游连接复用和缓冲策略,PHP-FPM 侧重点关注进程预算、请求队列与慢日志。本文给出参数含义、估算方法和核查流程,不提供“最优值”,因为本次没有可核验的压测记录。

核心要点 TL;DR
  • 先看时间花在哪一层:Nginx 等待上游、PHP 执行、数据库还是 I/O,再决定动哪个参数。
  • pm.max_children 应由单进程常驻内存与可用内存的预算推出来,而不是抄一个数字。
  • 每次只改一个变量并记录前后指标,否则无法判断改动是否有效。

“Nginx 与 PHP-FPM 怎么调优”常被当成一组参数模板来问。更准确的说法是:先确认瓶颈在哪一层,再改一个参数,再复测。脱离基线的参数复制不构成调优。本次没有提供可核验的压测记录或版本信息,所以下面只写机制、估算方法和核查流程,不给“最优值”;涉及具体默认值与版本行为的地方,需要按你实际部署的版本查证(待核实)。

一、先确认时间花在哪一层#

PHP 站点的延迟通常分布在四段:Nginx 自身处理、等待 PHP-FPM、PHP 执行(含框架与 OPcache)、以及 PHP 之外的下游(数据库、缓存、外部接口)。判断方法不复杂:

  • 对比 Nginx 访问日志里的 $request_time 与 $upstream_response_time。前者远大于后者,说明时间耗在 Nginx 或客户端链路;两者接近,说明瓶颈在 PHP-FPM 及其下游。
  • 开启 PHP-FPM 的 slowlog,查看慢请求的调用栈落在业务代码、数据库还是文件读写。
  • 系统层看 CPU、内存、磁盘等待与上下文切换,确认不是资源打满导致的排队。

这一步的产出是一句可核查的结论,例如“P95 延迟主要来自 upstream 等待”。没有这句话,后面的参数调整都缺少判断依据。

二、Nginx 侧:进程、连接与上游复用#

worker 进程与连接上限

worker_processes 通常按可用 CPU 核数设置,auto 会按核数取值。并发容量还受 worker_connections 与系统文件描述符上限共同约束;反向代理场景下,一个客户端连接会同时占用到上游的连接,实际可承载的客户端数量低于该值。具体换算需结合是否启用 keepalive,建议以压力测试结果为准。

到 PHP-FPM 的连接复用

如果 Nginx 与 PHP-FPM 走 TCP,每次请求都重新建连会带来额外开销;改用 Unix socket 或给 upstream 配置 keepalive 可以减少这部分成本。启用 upstream keepalive 时,需要把代理请求的 HTTP 版本设为 1.1,并清空 Connection 头,否则复用不会生效:

# 未执行示例:仅示意配置位置,数值需按实测确定
upstream php_backend {
    server unix:/run/php-fpm/www.sock;
    keepalive 32;              # 占位值
}

server {
    location ~ \.php$ {
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_pass http://php_backend;
    }
}

缓冲与超时

proxy_buffering 让 Nginx 先收完上游响应再逐步发给客户端,能缩短 PHP-FPM 进程的占用时间,但会引入磁盘临时文件;对响应体很大或需要流式输出的接口,需要单独评估。fastcgi_* 或 proxy_read_timeout 应大于正常请求的最长耗时,否则会把“慢”变成“错”。

三、PHP-FPM 侧:进程预算与队列#

pm 有三种模式:static 固定进程数,行为可预测;dynamic 按空闲进程伸缩;ondemand 按需创建,适合低频站点。选择依据是流量是否平稳,而不是哪种“更先进”。

max_children 用预算推,不要抄

常用估算方式是:可用内存 ÷ 单个 PHP-FPM 进程的常驻内存,并留出系统、Nginx 与数据库的余量。单进程常驻内存要在真实业务下观察,框架加载后的数值通常明显高于空进程。进程数超过内存预算后,系统可能触发 swap 或 OOM,延迟反而恶化。

排队、超时与慢日志

  • 请求量超过进程处理能力时,首先表现为延迟上升而不是报错;此时应看监听队列长度与 FPM 的 active/idle 进程数。
  • request_terminate_timeout 用于兜底终止失控请求,设置过小会误杀正常慢请求。
  • max_requests 通过定期重建进程缓解内存增长,代价是进程创建开销,是否启用取决于是否存在内存泄漏。

四、一次只改一个变量#

  1. 记录基线:固定压测工具、并发数、持续时间与数据规模,记录 P50/P95 延迟、吞吐、错误率和 CPU/内存。
  2. 提出假设:例如“upstream 等待占比高,复用连接应降低延迟”。
  3. 只改一个参数,其余保持不变,重新压测。
  4. 对比同口径指标;没有改善就回滚,不要叠加第二个改动。
  5. 记录版本、配置差异与结论,便于下一次复现。

上述流程未在本环境执行,属于建议方法;具体阈值与结果需在你自己的环境中测出。

五、边界:本文不覆盖什么#

不同 PHP 版本的 OPcache 与 JIT 行为、容器环境的内存限制与 CPU 配额、数据库慢查询、以及 TLS 握手成本,都会改变结论。本文没有可引用的官方文档或原始测试数据,参数默认值与版本差异请以对应版本文档为准(待核实)。如果你的瓶颈在数据库或业务代码,调整 Nginx 与 PHP-FPM 的参数通常只能改变排队方式,不能消除根因。

把调优看成“测量—假设—单变量变更—复测”的循环,比记住一组参数更有用。参数只是把已定位的瓶颈转化为配置表达;定位错了,参数再漂亮也只是把问题挪到别处。

常见问题(FAQ)

能不能直接给一组推荐参数?
不能。参数取值依赖机器内存、CPU、单请求内存占用和流量形态;本次没有可核验的压测记录,给出固定数值只会制造伪精度。
PHP-FPM 进程数开得越多越好吗?
不是。进程数超过内存预算会触发 swap 或 OOM,延迟反而上升;超过后端承载能力时,多出来的并发只是在排队。

作者:沐小悠

本文约 6 分钟读完 · 关键词:Nginx 调优、PHP-FPM、pm.max_children、upstream keepalive、slowlog

更多文章 GET 示例接口

「」标签下的其他文章