PHP-FPM 进程数计算公式:pm.max\_children 不是拍脑袋定的
pm.max\_children 的正确答案只有一个算法:**用「真正能分给 PHP-FPM 的可用内存 ÷ 单个 PHP-FPM 进程的平均 RSS(进程实际驻留内存)」,算出结果后向下取整,再留 10%\~20% 安全余量**。它既不是按 CPU 核数乘几,也不是照抄网上那句「512M 内存给 20 个」,更不该直接用面板默认值。
先记住公式,别被默认值带跑
结论:`pm.max_children = 可用内存(MB) ÷ 单进程平均 RSS(MB) × 安全系数(0.8~0.9)`。
这里有两个关键量:分子是扣掉 MySQL、Redis、Nginx、系统和其他服务之后剩下的内存,不是 `free` 里的 total;分母是你站点真实跑起来之后的进程内存,不是刚启动时的空闲值。PHP-FPM(FastCGI Process Manager,PHP 的进程管理器)的每个 worker 都是一份独立的 PHP 解释器 + 扩展 + opcache 共享页 + 请求上下文,站点越重、扩展越多,单个进程越大。
为什么不能拍脑袋?拍小了的代价是请求排队、nginx 报 502/504,日志里会出现「server reached pm.max_children setting」;拍大了的代价更惨——内存耗尽触发 OOM Killer(系统内存不足时的强制杀进程机制),第一个被杀的往往就是 MySQL,整站直接躺平。
第一步:算清能分给 PHP-FPM 多少内存
结论:可分配内存 = 总内存 − 其他常驻服务占用 − 系统与面板预留。
用 `free -m` 看总量,再用下面几条命令量主要服务的实际占用:
ps -o rss= -C mysqld | awk '{s+=$1} END {print "mysqld:", s/1024, "MB"}'
ps -o rss= -C redis-server | awk '{s+=$1} END {print "redis:", s/1024, "MB"}'
ps -o rss= -C nginx | awk '{s+=$1} END {print "nginx:", s/1024, "MB"}'
再给系统和面板留 500\~1000MB(宝塔这类面板本身还要跑 Python 进程和监控)。注意别把 buffer/cache 当成可用内存,`free` 里 available 那一列才是指标。
第二步:量出单进程真实 RSS
结论:必须在业务高峰时段采样,取平均值而不是最大值。
ps -o rss= -C php-fpm | awk '{s+=$1; n++} END {print "平均 RSS:", s/n/1024, "MB,进程数:", n}'
行业经验值供参考:空载的轻量站点 20\~35MB,正常跑动的社区论坛 40\~80MB,装了一堆重型扩展或含大数组处理逻辑的可以到 100MB 以上。这里说的是 RSS(Resident Set Size,进程驻留物理内存),它包含共享内存,所以算出来是偏保守的——保守对调参来说是好事。
第三步:代入计算,看一个 8G 机器的完整算例
结论:先算上限,再看流量需求,两者取小。
假设 4 核 8G 云服务器:总内存 8192MB,MySQL 占 1536MB,Redis 100MB,Nginx 80MB,系统与面板预留 800MB,额外缓冲 1024MB。
可分配 = 8192 − 1536 − 100 − 80 − 800 − 1024 = 4652MB。实测单进程平均 60MB,则 4652 ÷ 60 ≈ 77.5,取 70(已含安全系数)。
这时还要用利特尔法则验证一下需求端:所需并发进程数 ≈ 峰值 QPS × 平均响应时间(秒)。如果峰值 20 QPS、平均响应 0.25 秒,那 5 个进程就够,说明 70 是内存天花板而不是瓶颈,完全够用。
配套参数一起改,别只动一个
结论:`pm.max_children` 单独调没有意义,spare 参数和 `max_requests` 要配套。
用 dynamic 模式时:
pm = dynamic
pm.max_children = 70
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 45
pm.max_requests = 800
经验关系是 `start_servers` 取 max\_children 的 25%\~50% 且不低于 CPU 核数;`min_spare_servers` 约 25%,`max_spare_servers` 约 50%\~75%,且都不得小于 min。`pm.max_requests = 500\~1000` 让 worker 定期回收,防止个别扩展的内存泄漏把进程越撑越胖——这也是为什么 RSS 要定期复测。改完用 `php-fpm -t` 校验语法、`php-fpm -tt` 看合并后的完整配置,再 reload 而不是 restart。
另外两个容易漏的点:`ulimit -n` 要 ≥ max\_children × 4,否则连接数先爆;开启 `pm.status_path = /fpm-status` 并在 nginx 里限制内网访问,重点看 `max children reached` 和 listen queue 两个指标。
怎么判断调大了还是调小了
结论:看日志信号,不看感觉。
出现 `WARNING: [pool www] server reached pm.max_children setting` 且伴随 502/504,是调小了;`dmesg | grep -i "killed process"` 里有 mysqld 被杀,是调大了。还有一条铁律:max\_children 调大不能解决慢代码,单请求从 0.5 秒优化到 0.1 秒,等于白送 5 倍并发能力,优先级永远高于加进程。
回到开头:先量内存、再量单进程、代入公式、配套参数、上线后看监控复测——这套流程走一遍,你的 pm.max\_children 就是算出来的,不是猜出来的。
转载请注明出处,版权归原作者所有。
星耀SVIP
管理员
黑卡会员





