PHP-FPM 进程数怎么调:pm.max_children 与内存估算

    发布时间:2026-09-19 12:30 更新时间:2026-09-19 12:30 阅读量:0

    网站跑着跑着开始变慢,访问 phpMyAdmin 或者 Laravel 后台时页面转圈,过一会儿直接蹦出 502 Bad Gateway;去看监控,服务器内存还剩不少,CPU 也不高。这类现象八成不是代码出了问题,而是 PHP-FPM 的进程池被占满了。PHP-FPM 是常驻的进程管理器,它维护一批处理 PHP 请求的工作进程,池子里的进程全在忙的时候,新来的请求只能排队甚至被 Nginx 直接判为失败。调进程数这件事,靠拍脑袋填一个 pm.max_children = 100 很容易把内存吃爆,正确做法是先量出一个 PHP 进程到底占多少内存,再反推合理上限。

    先把 pm 三种模式的区别理清楚

    PHP-FPM 的进程池由 pm 参数决定管理策略,常见取值是 static、dynamic、ondemand 三种,它们的取舍完全不同。

    static 模式在启动时就一次性创建固定数量的工作进程,数量由 pm.max_children 决定,运行期间不变。好处是响应稳定,没有动态创建进程的开销,适合流量平稳、内存充裕的场景;坏处是低峰期也一直占着这么多内存,弹性差。

    dynamic 模式是绝大多数线上环境的默认选择。它用 pm.max_children 设定进程数上限,用 pm.start_servers 设定启动时创建的数量,用 pm.min_spare_servers 和 pm.max_spare_servers 控制空闲进程的上下限,再由 pm.max_requests 控制单个进程处理多少个请求后自动退出重建。忙碌时进程数往上加,空闲时回收,兼顾了性能和内存。

    ondemand 模式平时几乎不保留空闲进程,有请求才 fork,空闲超过 pm.process_idle_timeout 就退出。它最省内存,适合访问量很低、内存又特别紧张的小机器,代价是每次请求都要承担进程创建的开销,突发流量下响应会抖。

    需要提醒的是,pm.start_servers 一般建议落在 min_spare_servers 与 max_spare_servers 之间,否则 PHP-FPM 启动时会给出告警,具体校验规则以官方文档和实际版本为准。

    量出一个 PHP 进程的真实内存占用

    估算的前提是拿到单进程的常驻内存峰值,注意是 RSS 而不是虚拟内存 VSZ,PHP 进程的 VSZ 往往大得吓人,参考价值不大。最直接的办法是先让站点跑起来,等进程池里有一批稳定工作的进程后,用下面的命令按 RSS 排序查看:

    ps -ylC php-fpm --sort:rss | awk 'NR==1 || $8 > 0 {print $2, $8, $10, $NF}' | tail -n 20
    

    字段说明:第 8 列 RSS(KB)、第 10 列 %MEM、末列进程池名称

    如果服务器上跑了多个站点、多个进程池,要按池名区分统计,避免把不同业务的进程混在一起算平均值:

    ps -ylC php-fpm --sort:rss | awk 'NR>1 {sum[$NF]+=$8; cnt[$NF]++} END {for (p in sum) printf "%s 进程数=%d 平均RSS=%.1fMB 合计=%.1fMB\n", p, cnt[p], sum[p]/cnt[p]/1024, sum[p]/1024}'
    

    得到的平均 RSS 只能当参考,真正要关心的是峰值。比较稳妥的做法是取排序靠前的那几个进程的 RSS,或者连续采样几次取最大值。经验上前端框架类应用单进程 RSS 可能在 40MB 到 80MB 之间,跑着大数组、缓存或图像处理的接口能到 150MB 以上,这个数字和框架、扩展、opcache 配置强相关,必须自己实测,不要照抄别人的数值。拿到单进程峰值后,按下面这个式子反推:

    # 可用内存 = 总内存 - 系统与其他服务占用 - 预留缓冲
    

    pm.max_children = 可用内存 / 单进程峰值RSS

    示例:总内存 4GB,系统与 MySQL 等占用 1.5GB,单进程峰值 60MB

    (4096 - 1536) / 60 ≈ 42,再留一点余量,max_children 取 35 左右比较稳

    公式看着简单,坑在于「其他服务占用」经常被低估。同一台机器上如果还跑着 MySQL、Redis、Nginx,它们的内存必须先从总内存里扣掉;再留出 10% 到 20% 的缓冲应对突发和系统缓存,剩下的才是 PHP-FPM 可用的部分。宁可少给几个进程,也不要让机器进入 swap,一旦 PHP 进程被换出到磁盘,响应时间会成倍上涨,比直接排队还难受。

    一个 dynamic 模式的参考配置如下,数值只是示范,请按实测替换:

    ; /etc/php-fpm.d/www.conf 片段
    pm = dynamic
    pm.max_children = 35
    pm.start_servers = 8
    pm.min_spare_servers = 5
    pm.max_spare_servers = 15
    pm.max_requests = 500
    pm.status_path = /fpm-status
    request_terminate_timeout = 60s
    

    pm.max_requests 设一个几百到几千的值,可以让进程定期重建,缓解第三方扩展可能存在的内存缓慢增长问题;request_terminate_timeout 则给单个请求设一个兜底上限,避免某个卡死的请求长期占着进程。

    502 和排队,其实是同一件事的两面

    很多人把 502 当成 Nginx 的问题,实际上当 PHP-FPM 进程池满且监听队列也满时,Nginx 转发过去的连接会被拒绝或超时,日志里就是 upstream 相关的错误,浏览器看到 502。反过来,如果队列还有空位,请求不会立刻失败,而是安静地排队,表现为页面加载很慢、接口超时,但错误率不高,这种「假健康」状态更危险,因为监控上往往看不到明显异常。

    所以调进程数不只是为了消灭 502,更是为了让排队时间保持在一个可接受的范围。判断是否真的不够用,可以打开 FPM 的 status 页面观察:

    curl -s http://127.0.0.1/fpm-status?plain
    

    重点看 active processes、max active processes、listen queue、max listen queue

    如果 active processes 长期贴近 pm.max_children,listen queue 持续大于 0,说明池子确实小了,可以按前面算出的余量适度上调,同时检查是否有慢请求在长时间占用进程。若内存已经吃紧,上调进程数只会把问题从排队变成 OOM,此时更该做的是优化慢 SQL、给接口加缓存、把耗时任务挪到队列里异步处理,而不是继续加进程。

    小结与下一步

    调 PHP-FPM 的顺序可以固定下来:先用 ps 按 RSS 量出单进程的真实内存峰值,扣掉系统与其他服务占用并留出缓冲,反推 pm.max_children;再根据流量特征在 static、dynamic、ondemand 之间选模式,dynamic 适合大多数场景;最后通过 fpm-status 观察 active 与 listen queue,验证配置是否够用。改完配置记得 reload 而不是 restart,避免瞬间掐断正在处理的请求。下一次遇到 502,别急着改 Nginx,先去进程池和内存账本里找答案。

    继续阅读

    📑 📅
    Docker容器DNS解析异常排查:resolv.conf与自定义网络 2026-09-19
    MySQL连接被拒绝Connection refused逐层排查思路 2026-09-19
    systemd-journald 日志转发远程 syslog:rsyslog 对接与丢日志排查 2026-09-19
    Nginx 静态资源 404 与权限被拒排查:root、alias、try_files 的坑 2026-09-18
    Linux网卡丢包与TCP重传排查:ip -s link、ss -ti与ethtool实战 2026-09-18
    Nginx gzip 与 Brotli 压缩配置实战:静态资源体积优化 2026-09-19
    Docker 容器内 CPU 被限流排查:cfs_quota、cpuset 与 top 显示异常 2026-09-19
    Nginx 与后端长连接调优:keepalive 与 upstream 复用 2026-09-20
    Docker 容器日志写满磁盘:json-file 限制与 max-size 配置 2026-09-20
    Linux时间与时区排查:date、timedatectl与容器时区一致性 2026-09-20