登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  php教程

PHP-FPM 子进程打满导致 502 怎么排查:从 Nginx 错误日志到 pm.max_children 调整

来源:17golang原创

时间:2026-07-09 10:55:28 407浏览 收藏

线上 PHP 站点突然出现 502,大家最先想到的往往是 Nginx 出问题、机器带宽跑满或者某个业务接口挂了。但如果报错全都集中在访问高峰期,翻 Nginx 错误日志反复能看到 upstream timed outconnect() to unix:/run/php/php-fpm.sock failed,同时查 PHP-FPM 状态页发现空闲子进程数已经掉到 0,说明问题早就从单个慢请求蔓延开,变成了整个进程池的拥堵。碰到这种情况别上来就直接调大 pm.max_children,先把占住子进程的请求定位清楚,做出来的修复才不会是只能撑几分钟的临时对策。

要点速览
  • 502 报错如果伴随 PHP-FPM 空闲子进程为 0,优先按进程池拥堵排查,不用死磕 Nginx 配置。
  • pm.max_children 可以快速止血,但根因几乎都出自慢 SQL、外部接口等待或资源锁竞争。
  • 要把慢日志、状态页数据、Nginx upstream 错误三类信息放到同一时间线里对照分析。
  • 故障恢复建议分两步走:先压下错误率,再逐一清理长时间占用子进程的异常请求。

影响面:502 先从少量接口冒出来

这类故障几乎不会出现全站一秒钟全挂的情况。最常见的过程是订单列表、导出报表、支付回调这类本身就偏慢的接口先报 502,之后普通页面也逐渐开始响应变慢。因为 PHP-FPM 的子进程是所有请求共享的资源,一批请求被慢 SQL 或者外部接口调用卡住之后,新进来的请求只能排队,排队时长超过 Nginx 的网关等待上限就会直接抛出 502。

排查的第一步先理清楚用户侧的实际影响:哪些接口报了错、错误占比有多高、持续了多长时间、是不是只出现在某一个机房或者某几台机器上。摸清楚这些信息之后,才能判断后续操作是直接扩容、摘下线异常机器、做功能降级,还是直接回滚最近的上线版本。

现象 可能含义 优先排查位置
只有少量接口报 502 慢请求在局部堆积 Nginx upstream 访问日志
全站所有请求响应都变慢 FPM 子进程已经接近满载 PHP-FPM status 状态页
机器 CPU 不高但请求持续堆积 I/O 等待、锁等待或外部调用等待 PHP 慢日志和数据库监控面板
重启 PHP-FPM 之后服务短暂恢复很快又报错 进程队列被清空但根原因没消除 故障恢复前后的完整时间线数据

时间线:先把 Nginx 和 PHP-FPM 的证据对齐

502 刚出现的时候,单拎某一条日志出来看很容易判断错方向。Nginx 只能感知到后端没在约定时间内返回数据,PHP-FPM 状态页能告诉你整个进程池是不是已经被占满,慢日志才能明确告诉你具体是哪些 PHP 请求卡住了。把三类信息放到同一分钟的时间轴里对照,根因很快就能显出来。

PHP-FPM 子进程满载时 Nginx 502 升高并出现慢日志的故障时间线

一个典型的故障时间线大概是这样:

10:02  订单列表 P95 从 420ms 升到 3.8s
10:04  Nginx 出现 502 Bad Gateway
10:05  PHP-FPM status: active processes 40, idle processes 0
10:06  slowlog 记录 /order/export.php 等待数据库返回
10:08  临时下线导出入口,502 错误率开始下降

这里有个很实用的判断点:如果当时机器 CPU 已经跑满,大概率是 PHP 代码本身计算逻辑太重;如果 CPU 占用率不高但 FPM 子进程已经全满,绝大多数情况是请求在等数据库返回、等 Redis 响应、等第三方接口回调或者等文件 I/O 操作。别被“机器资源使用率还很低”的表象误导,PHP-FPM 进程池占满之后,整个入口链路照样会被堵死。

触发条件:慢请求长期占住子进程

PHP-FPM 用的是进程池模型。每一个请求进入之后,会独占一个 worker 子进程,直到请求完全处理完才会释放资源。原本耗时几百毫秒的正常接口,一旦因为某些异常变到十几秒才能返回,几十个并发就足以把整个进程池占满。

先打开状态页确认是不是进程池层面的问题,相关配置可以放到内网管理端访问,或者只给本机开放权限,避免直接暴露在公网带来安全风险。

pm.status_path = /fpm-status
ping.path = /fpm-ping
request_slowlog_timeout = 3s
slowlog = /var/log/php-fpm/www-slow.log

状态页里重点关注这几个字段:

  • active processes:正在处理请求的子进程数量。
  • idle processes:空闲可用的子进程数量,长时间掉到 0 就要立刻警惕。
  • listen queue:当前已经排队、等待进入进程池的请求数量。
  • max children reached:进程池达到子进程上限的累计次数。

如果 max children reached 这个数值一直在上涨,说明已经有大量请求因为进程池容量不足开始排队,这个时候单纯重启 PHP-FPM 只能让服务恢复一小会,等之前的慢请求再次进来之后,还是会很快把进程池占满。

根因:容量不足只是表象,慢请求才是核心原因

pm.max_children 设置太小确实会放大故障影响,但它几乎不会是问题的唯一原因。真正要深挖的点是:为什么原本正常的单个请求耗时突然变长了?哪些请求占住了 worker 子进程?这些请求有没有必要在业务高峰期同步执行完?

慢日志可以直接看到卡住的 PHP 文件名和完整调用栈。比如导出接口里某条 SQL 漏加合适索引,把几十个导出请求的耗时拖到 8 秒以上,FPM 进程池很快就会被全部占满。

[pool www] pid 18492
script_filename = /srv/app/public/order/export.php
request_uri = /order/export?status=paid
duration = 8.42s

PDOStatement->fetchAll()
OrderExportService->buildRows()
ExportController->download()

拿到慢日志之后,不要只笼统记录“导出接口运行慢”。更有参考价值的记录内容是对应接口名、SQL 指纹、扫描行数、锁等待状态、外部接口耗时、发生时间点和当时的并发量。这样后续的修复动作才能精准落到具体对象上,而不是泛泛说一句优化性能。

修复动作:先恢复服务可用性,再慢慢处理慢请求

故障处理可以拆成两步走。第一步先把 502 的错误占比压到可接受范围,第二步再慢慢处理慢 SQL、拆分同步队列、新增限流规则和把耗时长的逻辑后台化。生产环境里这两个动作经常需要并行推进,但要分清楚哪部分是临时止血,哪部分是彻底根治。

PHP-FPM 故障恢复时先扩容进程再拆慢查询并用限流恢复服务

常用的止血操作包括:

  • 临时调高 pm.max_children 数值,前提是机器可用内存足够,别直接把机器撑到触发 OOM。
  • 先把异常流量的机器摘出负载均衡,或者临时关掉高耗时的功能入口,比如大体积报表导出。
  • 给高耗时接口加网关层面的限流,避免新的请求继续涌进来把整个进程池占满。
  • 把可以异步处理的任务改成后台队列执行,前端只返回任务编号和后续进度查询入口。

容量调整别随手拍脑袋。先估算单个 PHP-FPM 子进程的常驻内存占用,再结合机器剩余可用内存设置上限。比如单个子进程峰值内存大概是 80MB,机器预留好系统和 Nginx 的内存占用之后,可以分给 FPM 6GB 内存,这种场景下 pm.max_children 就没必要随手配置到 200。

pm = dynamic
pm.max_children = 80
pm.start_servers = 12
pm.min_spare_servers = 8
pm.max_spare_servers = 20
request_terminate_timeout = 30s

上面的数值只是示例,真实环境的配置要结合单个进程内存占用、日常流量峰值和接口平均耗时压测之后得出。尤其是 request_terminate_timeout 这个配置项,它可以拦截掉异常长的请求,但设置得太短也可能误伤正常的大文件上传或者导出场景。

防复发:把慢日志、容量基准和告警一起补上

这次 502 故障恢复之后,别只在故障单里写一句“已恢复”就结束。建议补三类优化项:容量基准数据、慢请求治理、告警阈值配置。不然下次高峰期换另一个接口变慢,还是会用同样的方式把 PHP-FPM 进程池拖垮。

  • 容量基准:记录每台机器的 pm.max_children、单进程峰值内存、平均请求耗时。
  • 慢请求治理:把慢日志里记录的接口按耗时和访问频次排序,优先改高峰期的同步链路逻辑。
  • 数据库侧检查:定期巡检 SQL 指纹、扫描行数、锁等待情况和缺失的索引。
  • 告警阈值:监控 idle processeslisten queuemax children reached 指标。
  • 降级开关:报表导出、批量刷新、第三方数据同步这类功能要留可以一键关闭的管理入口。

一个很实用的告警规则是:空闲子进程数值连续 2 分钟低于 2,或者 listen queue 计数持续上涨,就立刻提醒值班人员去查慢日志,别等 502 大面积出现了才开始到处翻日志定位。

常见问题

PHP-FPM 子进程满了,直接调大 pm.max_children 行不行?

可以作为临时止血手段,但不能当成完整的修复方案。调大上限之后会消耗更多内存,如果慢请求本身没处理,只是让更多请求同时卡住,最后故障不会消失,反而可能从 502 演变成机器内存 OOM 告警。

为什么 CPU 占用不高也会出现 502?

PHP 请求很多时候是在等数据库、Redis、文件 I/O 或者第三方接口返回数据。等待的这段时间 CPU 看起来空闲,但对应的 worker 子进程并没有释放资源,新的请求照样没法拿到可用子进程执行。

PHP-FPM slowlog 打开之后会不会影响性能?

正常配置阈值下性能损耗非常小。建议只记录执行时长超过 2 到 5 秒的请求,优先用于故障定位;长期开启慢日志的话要配套做好日志轮转配置,避免磁盘空间被日志文件占满。

502 和 504 怎么区分?

502 更多出现在后端连接失败或者返回异常响应的场景,504 代表网关等待后端返回超时。实际排查的时候不要只盯着状态码区分问题,核心还是要把 Nginx upstream 错误、FPM 状态数据和慢日志内容对应上。

小结

PHP-FPM 子进程打满导致的 502,本质是“流量入口还在不断接请求,但后端可用的 worker 子进程已经没有余量了”。稳妥的处理方式是先用 Nginx 日志和 FPM 状态页确认是进程池拥堵,再从慢日志里找到长时间占用 worker 的异常请求。pm.max_children 可以帮你快速争取到故障恢复的缓冲时间,但慢查询优化、外部调用等待处理、限流规则配置和提前告警机制才是决定下次不会被同类问题打穿的核心。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>