本文约 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 不够或内存不够,优先加内存而不是硬调大进程数。
常见踩坑
- OOM 被杀但不知道:查看内核日志:
bash
sudo dmesg -T | grep -i "killed process"
sudo grep -i "out of memory" /var/log/syslog
看到 Killed process 1234 (php-fpm8.2) 就是内存超了。
- sock 权限问题:Nginx 用
www-data用户,listen.owner必须一致,否则 502 且日志显示Permission denied。 max_requests设 0:永不重启进程,内存泄漏会累积,跑几天内存就满了。设 500–1000 比较合适。- 案例:某站 2G 内存,
max_children=100,白天正常,晚上流量高峰必 502。查 dmesg 发现 OOM Kill。按公式重算是(2048-512-512-128)/42 ≈ 21,改成 20 后反而稳定了,因为不再 OOM。进程数不是越多越好。 request_terminate_timeout和 Nginxfastcgi_read_timeout不一致:前者先触发会返回 502,后者先触发返回 504,日志里看到的现象不同。建议前者 ≥ 后者。- 导出 Excel 之类的长任务被杀:可以把这类请求走独立的 pool,单独设大的 timeout 和小的进程数,避免拖垮主站。
- 开启 opcache 后再算内存:开 OPcache 会增加每个进程的内存(共享内存部分),但整体更快。测内存要在最终配置下测。
总结
pm.max_children 没有通用值,必须按你的机器内存和单进程实际占用算。三步走:ps 测真实 RSS → 扣掉系统和其他服务 → 除以单进程内存并留 20% 余量。上线后开 /fpm-status 监控 max children reached 和 listen queue 两个指标,用 ab 压测验证。记住:502 不一定是进程数不够,也可能是内存不够被 OOM 杀了,先查 dmesg。进程数调优属于”压榨单机性能”的一环,更大收益通常来自Nginx FastCGI 缓存配置这类减少 PHP 执行的方案。
