搜索全站
报错排查 12 分钟阅读

PHP-FPM 进程数怎么算:max_children 计算公式与压测验证

服务器内存多大就决定了能开多少个 PHP-FPM 进程。本文给出 max_children 的计算公式、三种进程管理模式的适用场景、真实内存测量方法,以及 502 错误的定位思路。

U
UFO_PF 2026-05-25 · 阅读 12 分钟
PH 报错排查
阅读提示
本文约 4,417 字,预计阅读 12 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:进程数设错了会发生什么

pm.max_children 是 PHP-FPM 最关键的参数,设错的两个方向都会出问题:

设太大:内存不够,Linux OOM Killer 杀掉进程,表现为间歇性 502、数据库连不上、随机报错。这种情况最难查,因为日志里只有 Out of memory: Kill process 1234 (php-fpm),混在系统日志里。

设太小:并发上来后请求排队,用户感觉”卡”,Nginx 日志出现 connect() to unix:/run/php/php8.2-fpm.sock failed (11: Resource temporarily unavailable),或者直接 502(backlog 满了)。

很多人直接抄网上的 max_children = 50,在 1G 内存的小机器上开 50 个进程,每个进程 40MB,那就是 2GB——必炸。

原理分析:PHP-FPM 的三种进程管理模式

pm = dynamic | static | ondemand
模式 行为 适用场景
static 启动时一次性 fork 固定数量的子进程,全程不变 内存充足、流量稳定、追求最低延迟(省去 fork 开销)
dynamic 启动时开 start_servers 个,按负载在 min_spare_servers 和 max_spare_servers 之间伸缩,上限 max_children 绝大多数场景的默认选择
ondemand 有请求才 fork 进程,空闲 pm.process_idle_timeout 后回收 内存极小、流量很低(如后台管理站);缺点是突发流量时响应慢

关键参数(dynamic 模式):

pm.max_children        ; 硬上限,超过的请求排队
pm.start_servers       ; 启动时进程数
pm.min_spare_servers   ; 最少空闲进程(低于此值就 fork 新的)
pm.max_spare_servers   ; 最多空闲进程(高于此值就 kill 掉)
pm.max_requests        ; 每个进程处理多少请求后重启(防内存泄漏)

注意 max_spare_servers 不能超过 max_children,否则 FPM 启动报错。

保姆级实操

第一步:测量单个 PHP-FPM 进程实际占多少内存

这是计算的基础,不能靠猜。

# 查看各进程的 RSS(实际物理内存,单位 KB)
ps -o pid,rss,cmd -C php-fpm8.2 --sort=-rss | head -10

输出类似:

  PID   RSS CMD
 1234  48200 php-fpm: pool www
 1235  44120 php-fpm: pool www
 1236  39880 php-fpm: pool www

重要:取的是运行一段时间后、处理过真实请求的进程,不是刚启动时的。刚启动只有 15–20MB,跑一会儿加载插件后会涨。建议让站点正常跑一天,挑峰值时段测。

更准确的做法是算平均值:

ps --no-headers -o rss -C php-fpm8.2 | awk '{s+=$1} END {print "平均:", s/NR/1024, "MB", "总数:", NR}'

第二步:套公式

max_children = (可用内存 - 系统预留 - 其他服务占用) / 单进程内存

举例:4GB 内存的机器,跑 Nginx + PHP-FPM + MySQL:

总内存            4096 MB
- 系统预留         -512 MB
- MySQL            -1024 MB(innodb_buffer_pool_size)
- Nginx 及其他     -256 MB
= PHP 可用         2304 MB
÷ 单进程 45 MB
= max_children     ≈ 51 → 保守取 45

保守系数:永远留 20% 余量。突发流量时进程内存会涨(处理大图、大查询结果),算满必 OOM。

第三步:写配置

编辑 /etc/php/8.2/fpm/pool.d/www.conf:

[www]
user = www-data
group = www-data
listen = /run/php/php8.2-fpm.sock
listen.owner = www-data
listen.group = www-data

pm = dynamic
pm.max_children = 45
pm.start_servers = 8
pm.min_spare_servers = 4
pm.max_spare_servers = 12

; 每个进程处理 500 个请求后重启,防内存泄漏
pm.max_requests = 500

; 慢日志:超过 5 秒的请求记录调用栈
slowlog = /var/log/php8.2-fpm.slow.log
request_slowlog_timeout = 5s

; 单请求超时(要和 Nginx 的 fastcgi_read_timeout 配合)
request_terminate_timeout = 60s

; 开启状态页,便于监控
pm.status_path = /fpm-status

start_servers 的经验值:min_spare_servers + (max_spare_servers - min_spare_servers) / 2。

第四步:Nginx 配合

location = /fpm-status {
    access_log off;
    allow 127.0.0.1;
    deny all;
    include fastcgi_params;
    fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
curl http://127.0.0.1/fpm-status

输出关键字段:

pool:                 www
process manager:      dynamic
start time:           01/Jan/2025:00:00:00 +0800
accepted conn:        128456
listen queue:         0        ← 排队数,>0 说明进程不够
max listen queue:     12
idle processes:       6
active processes:     9
total processes:      15
max active processes: 45       ← 曾达到的最大并发
max children reached: 3        ← 达到上限的次数,>0 说明要调大
slow requests:        17

max children reached 大于 0 是明确的扩容信号。

第五步:压测验证

# 安装压测工具
sudo apt install apache2-utils -y

# 50 并发,共 1000 个请求
ab -n 1000 -c 50 https://mimangfei.com/

观察:

  • Failed requests 应为 0;
  • Time per request 和 Requests per second;
  • 同时用 htop 看内存,用 watch 'curl -s http://127.0.0.1/fpm-status' 看队列。

如果压测时 listen queue 持续 > 0 或出现 502,说明 max_children 不够或内存不够,优先加内存而不是硬调大进程数。

常见踩坑

  1. OOM 被杀但不知道:查看内核日志:

bash
sudo dmesg -T | grep -i "killed process"
sudo grep -i "out of memory" /var/log/syslog

看到 Killed process 1234 (php-fpm8.2) 就是内存超了。

  1. sock 权限问题:Nginx 用 www-data 用户,listen.owner 必须一致,否则 502 且日志显示 Permission denied。
  2. max_requests 设 0:永不重启进程,内存泄漏会累积,跑几天内存就满了。设 500–1000 比较合适。
  3. 案例:某站 2G 内存,max_children=100,白天正常,晚上流量高峰必 502。查 dmesg 发现 OOM Kill。按公式重算是 (2048-512-512-128)/42 ≈ 21,改成 20 后反而稳定了,因为不再 OOM。进程数不是越多越好。
  4. request_terminate_timeout 和 Nginx fastcgi_read_timeout 不一致:前者先触发会返回 502,后者先触发返回 504,日志里看到的现象不同。建议前者 ≥ 后者。
  5. 导出 Excel 之类的长任务被杀:可以把这类请求走独立的 pool,单独设大的 timeout 和小的进程数,避免拖垮主站。
  6. 开启 opcache 后再算内存:开 OPcache 会增加每个进程的内存(共享内存部分),但整体更快。测内存要在最终配置下测。

总结

pm.max_children 没有通用值,必须按你的机器内存和单进程实际占用算。三步走:ps 测真实 RSS → 扣掉系统和其他服务 → 除以单进程内存并留 20% 余量。上线后开 /fpm-status 监控 max children reached 和 listen queue 两个指标,用 ab 压测验证。记住:502 不一定是进程数不够,也可能是内存不够被 OOM 杀了,先查 dmesg。进程数调优属于”压榨单机性能”的一环,更大收益通常来自Nginx FastCGI 缓存配置这类减少 PHP 执行的方案。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。