用户登录

技术文章

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

Nginx 与 PHP-FPM 调优的实战循环:先建立基线,再让每次改动可归因

一句话回答

Nginx 与 PHP-FPM 调优的实战重点不是找一组最优参数,而是把参数调整变成可归因的循环:固定测量标准,用 request_time 与 upstream_response_time、FPM status、slowlog 判断瓶颈落在连接层、进程池还是应用与下游;再按内存与下游承载能力反推进程池上限,一次只改一个变量并同条件回归。单请求内部慢时增加进程只会放大下游压力。本次未联网核验,默认值与版本差异待对照官方文档。

Nginx 与 PHP-FPM 的调优,实际工程中最难的部分通常在于判断该改哪个参数,以及改完之后能否说清收益来自哪里;“最优参数”本身反而不是难点。没有稳定基线,任何调整都只是猜测:响应变快了,可能是因为同时换了机器、改了缓存或换了流量结构。本文只给可复用的方法与边界;具体默认值、指令行为随 Nginx 与 PHP-FPM 版本变化,待核实,请对照所使用版本的官方文档。

先把耗时拆到三层#

排查前先确定瓶颈属于哪一层,否则容易在错误的层里反复调参。

  • Nginx 层:连接建立、TLS 握手、静态文件读取、响应缓冲。特征是 Nginx 自身耗时高,而转发到后端的耗时不高。
  • PHP-FPM 层:进程池排队、可用进程耗尽、慢请求长期占用 worker。特征是请求在等待被处理,而不是处理得很慢。
  • 应用与下游层:数据库、外部 HTTP 接口、锁竞争。特征是单个请求内部耗时长,与并发数关系不大。

一个常用判据是在访问日志中同时记录 $request_time(Nginx 视角的整请求耗时)与 $upstream_response_time(后端响应耗时)。两者接近,说明时间花在后端;差距大,则要检查客户端、网络与缓冲。这只是定位线索,不是结论。

参数之间是耦合的#

把参数当成彼此独立的开关,是调优里最常见的误判。下表是机制层面的关系,不代表推荐取值。

参数实际影响必须同时考虑
pm.max_children可同时处理的请求数上限单进程常驻内存 × 进程数 与可用内存的关系;下游(数据库、外部 API)的连接与承载上限
pm 模式static 常驻、dynamic 伸缩、ondemand 更积极回收伸缩本身有创建进程的开销;突发流量下 dynamic 可能来不及扩容
listen.backlog等待被 accept 的连接队列长度队列变长不提升处理能力,只是把拒绝推迟成超时
pm.max_requests进程处理若干请求后重建缓解内存增长,但每次重建都有成本
fastcgi_buffers 等响应体超出缓冲会写入临时文件大响应场景下磁盘 I/O 可能成为新瓶颈

进程池上限的算法是「可用于 PHP 的内存 ÷ 单进程常驻内存(RSS)」,其中 RSS 必须在真实流量下测量,而不是取空载值。此外,进程数超过数据库连接池或外部接口的承载能力时,故障会被放大。

让每次改动可归因#

  1. 固定负载:用同一份压测脚本或同一时段的真实流量作为对照,记录并发数、请求分布与数据集大小。
  2. 记录基线:至少保留耗时分布(而非仅平均值)、错误率、进程数与内存。
  3. 一次只改一个变量,其余保持不动。
  4. 同条件重跑,比较分布而不是单点;差异在噪声范围内就回滚。
  5. 留下记录:改了什么、为什么改、结果如何。否则下一轮又会从头猜。
压测流量与真实流量的比例、缓存命中率往往不同。压测结论只能说明该条件下的行为,不能直接外推为线上收益。

最小定位配置(示意,未执行)#

以下片段仅用于说明需要采集哪些信号,未在本环境执行,路径与指令名请按实际环境核对。

# 示意配置,未执行
log_format timed '$request $status rt=$request_time urt=$upstream_response_time';
access_log /var/log/nginx/timed.log timed;

location = /fpm-status {
    allow 127.0.0.1;   # status 页应限制来源
    deny all;
    include fastcgi_params;
    fastcgi_param SCRIPT_NAME /fpm-status;
    fastcgi_pass unix:/run/php/php-fpm.sock;
}
; php-fpm pool 示意,数值为占位演示,不是推荐值
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/slow.log
request_terminate_timeout = 30s
pm = dynamic
pm.max_children = 32
pm.max_requests = 500

FPM status 中的 listen queue(等待队列)与 active/idle 进程数,是区分「排队」与「处理慢」的关键;slowlog 给出的是慢请求的 PHP 调用栈,用来判断慢在应用内部哪一段。

什么时候不该动进程池#

如果慢请求集中在调用外部服务的那一段(例如 PHP 侧请求第三方 API),增加 pm.max_children 只会把更多并发压向对端,可能同时触发对端限流与本地超时堆积。这类问题应先在调用层解决:设置合理超时、失败快速返回、缓存可复用结果、区分可重试与不可重试错误。进程池调优解决的是「有活干却没进程接」,不是「活本身太慢」。

本次写作未联网核验,也未运行任何压测或配置;文中不包含实测数据。上述机制性说明需结合自家版本与指标复核,任何具体取值都应在自己的基线上验证后再采用。

作者:沐小悠

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

更多文章 GET 示例接口

「」标签下的其他文章