用户登录

技术文章

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

Nginx 与 PHP-FPM 性能调优:先测量,再改配置

一句话回答

从请求链路拆解 Nginx 与 PHP-FPM 的调优位置:先确认瓶颈在排队、进程、上游超时还是应用本身,再分别调整连接队列、worker 与进程池参数、超时与缓冲,并用压测前后对比验证;本文不含实测数据,参数取值需按部署环境核实。

做 Nginx 与 PHP-FPM 调优,最容易踩的坑不是参数不会写,而是顺序反了:先改配置再看效果。本文没有联网素材,也不含实测数据,因此不给出“某参数调到多少最优”的结论。可回答的窄问题是:这条请求链路上有哪些环节值得调、每个参数影响什么、改动前该看哪些指标、哪些取值必须回到你的部署环境核实。

先分清瓶颈在哪一段#

一个 PHP 请求通常经过:客户端 → Nginx 监听队列 → Nginx worker 处理 → FastCGI 连接 → PHP-FPM 进程池 → 应用(数据库、外部 API)。调优前应先判断延迟堆在哪一段,否则同时改 Nginx 和 FPM 参数,出问题时无法归因。

  • 连接被拒或握手慢:看 Nginx 的 listen 队列与 worker 数量。
  • 请求在 Nginx 内部等待:看 worker 连接数上限、缓冲与超时设置。
  • 请求进了 FPM 才变慢:看进程池是否打满、排队长度、单请求耗时。
  • 进程池空闲仍慢:瓶颈大概率在应用下游,改 Nginx 或 FPM 参数收益很小。

Nginx 访问日志中的 $request_time 与上游响应时间 $upstream_response_time 的差值,是区分“Nginx 侧耗时”和“PHP 侧耗时”的常用手段;两个变量需在 log_format 中显式配置,具体可用变量以所用版本的官方文档为准(待核实)。

Nginx 侧:队列、worker 与上游连接#

监听队列

listen ... backlog= 控制等待 accept 的连接队列长度,net.core.somaxconn 是内核侧的同名上限。实际生效值取两者较小者,这一点在 Linux 上属于系统行为,具体默认值随内核版本变化,需在目标机器上查看(待核实)。队列溢出通常表现为客户端连接超时或重试,而不是应用报错。

worker 数量与连接上限

worker_processes auto 让 Nginx 按可用 CPU 数派生 worker,是常见起点;是否需要偏离,取决于进程是 CPU 密集还是等待 I/O。worker_connections 决定单 worker 可同时处理的连接数,注意反向代理场景下一个客户端请求会占用两条连接(客户端侧与上游侧),因此实际可承载的并发请求数小于该值。

上游超时与缓冲

fastcgi_read_timeout、fastcgi_connect_timeout、fastcgi_send_timeout 定义 Nginx 等待 PHP-FPM 的边界。把它们一味调大,会把 PHP 侧的真实缓慢掩盖成“页面很慢但不报错”;调得过小,则会把正常的慢请求变成 504。合理做法是先测出应用 P95 耗时,再让超时略高于该值,并配合日志观察被截断的请求。

fastcgi_buffers 与 fastcgi_buffer_size 影响响应头的缓冲;响应头过大(例如携带大量 Cookie 或重定向头)时,Nginx 会记录 “upstream sent too big header” 类错误,此时才需要调大,而不是默认全调。

PHP-FPM 侧:进程池模型比数字更重要#

PHP-FPM 的 pm 有三种模式,选择比调参更关键:

模式行为适用方向
static固定数量的子进程,不伸缩负载稳定、内存可预估
dynamic在 min/max 之间按空闲进程数伸缩负载波动,需接受伸缩带来的启动开销
ondemand无请求时进程可退出到零低流量或内存紧张,代价是冷启动延迟

关键约束是内存:每个 PHP 子进程的实际常驻内存乘以 pm.max_children,不应超过为 FPM 预留的内存。单个进程的内存占用需在真实应用上采样,不能套用通用数字(此处不给参考值,待核实)。

pm.max_children 设得过小,请求会在监听队列里排队,表现为响应时间整体抬高而 CPU 不高;设得过大,则可能触发内存交换,反而让所有请求变慢。pm.max_requests 让子进程处理若干请求后重启,用于规避第三方库的内存泄漏,代价是额外的进程回收开销。请求排队情况可通过 FPM 的 status 页观察,需在池配置中开启 pm.status_path 并限制其访问来源(示例配置未执行,仅示意):

; 演示配置,未在本文环境执行
[www]
pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
pm.max_requests = 500
pm.status_path = /fpm-status

上述数值仅为占位示例,不可直接用于生产;请按内存上限与实测单请求耗时反推。status 页应只允许本机或内网访问,不要暴露到公网。

容易被忽略的两个前提#

  • OPcache:PHP 代码缓存未开启时,每个请求都要重新编译脚本,此时调 FPM 进程数是在放大低效。相关参数在 php.ini 中配置,是否启用及缓存大小需按版本核实。
  • 长连接与 keepalive:Nginx 到上游默认短连接,每次请求重建 TCP 连接有固定开销;启用 upstream keepalive 需同时设置 keepalive 与协议版本,配置组合不正确时不会生效(具体写法以官方文档为准)。

验证方式与边界#

任何参数调整都应有可对比的前后数据:同一压测工具、同一并发梯度、同一请求混合比例,记录吞吐、P95 延迟与错误率。本文未运行任何压测,不提供性能数字。若无法复现稳定的测试环境,至少应在灰度实例上对比,并保留回滚到原配置的路径。

还有一类问题不适合在 Nginx/FPM 层解决:单请求内部的外部 API 调用超时、数据库慢查询、无索引分页。这些会表现为 FPM 进程被长时间占用,此时增加 pm.max_children 只会把更多进程堵在同一处。API.PLUS 的调用教程中提到的超时与错误边界设置,思路与此一致:先给下游依赖设定明确超时,再谈并发容量。

调优的产出不是一个“最优配置”,而是一组能解释瓶颈的证据,以及一条可回退的改动路径。

作者:沐小悠

本文约 7 分钟读完 · 关键词:Nginx、PHP-FPM、性能调优、fastcgi、进程管理

更多文章 GET 示例接口

「」标签下的其他文章