PHP-FPM 进程耗尽:pm.max_children 用内存反推怎么算

来源:互联网 时间:2026-09-04

间歇性 502 里最磨人的一种:低峰期一切正常,流量一上来就崩,过会儿又自己好了。FPM 日志里翻出 server reached pm.max_children,基本就能确诊——子进程开到上限,新请求没进程可用了。

很多教程看到这条日志就让你把 pm.max_children 调大,却不说调到多少。拍脑袋填个 100,机器 2G 内存,结果就是 502 治好了,OOM 上门了——内存不够,内核开始杀进程,这次死的可能是 MySQL。

正确的算法是拿内存反推。先量出一个 PHP 子进程实际占多少内存,再用可用内存除一下。所谓可用内存,是总内存减去系统开销、减去 Nginx、减去 MySQL 这类常驻服务,剩下的才轮得到 PHP 分。

量单进程内存有个讲究:不能直接看 ps 里的 RSS 平均值,因为刚启动的进程占用小,跑过一轮请求的进程占用大。要取跑过请求之后的稳态值,取所有进程的 RSS 平均值作为参考,再留两成余量。

#!/bin/bash
# pm.max_children 内存反推计算
# 用法:bash calc_max_children.sh
# 参数:可用给 PHP 的内存(MB),不填则按当前空闲内存的 60% 估算

RESERVE_MB=${1:-0}

# 1. 取所有 php-fpm 子进程的 RSS,算平均值(KB)
AVG_KB=$(ps -o rss= -C php-fpm 2>/dev/null | awk "{s+=\$1; n++} END {if(n>0) printf \"%d\", s/n; else print 0}")

if [ "$AVG_KB" -eq 0 ]; then
echo "没取到 php-fpm 进程,确认服务是否运行"; exit 1
fi

AVG_MB=$((AVG_KB / 1024))

# 2. 可用内存:没指定就按空闲内存的 60% 算(保守留余量)
if [ "$RESERVE_MB" -eq 0 ]; then
FREE_MB=$(free -m | awk "/^Mem:/ {print \$7}")
RESERVE_MB=$((FREE_MB * 60 / 100))
fi

# 3. 反推进程数,再打八折留安全边界
RAW=$((RESERVE_MB / AVG_MB))
SAFE=$((RAW * 80 / 100))

echo "单进程平均内存 : ${AVG_MB} MB"
echo "可分配给PHP : ${RESERVE_MB} MB"
echo "理论上限 : ${RAW}"
echo "建议 max_children: ${SAFE}(已留20%余量)"
echo ""
echo "配套的 dynamic 配置建议:"
echo "pm.max_children = ${SAFE}"
echo "pm.start_servers = $((SAFE / 4))"
echo "pm.min_spare_servers = $((SAFE / 8))"
echo "pm.max_spare_servers = $((SAFE / 3))"
echo "pm.max_requests = 500"

pm.max_requests 这行别漏。它规定每个子进程处理多少个请求后就自动重启,用来兜底 PHP 代码里的内存泄漏——跑久了进程会越来越胖,漏得慢的时候你可能一两个月都发现不了,等发现时已经吃掉了大半内存。设成 500 左右,进程会被温和地轮换掉,代价几乎可以忽略。

三种进程管理模式的取舍也得说清。static 是一开始就把进程全开好,响应最快但内存常驻占用高;dynamic 按需增减,适合大多数站点;ondemand 用到了才 fork,最省内存但高并发时 fork 本身的开销会拖慢响应。小内存机器上跑访问量不大的站,ondemand 是合理选择;有稳定流量的站用 dynamic 更稳。

改完怎么验?别只看页面能打开。用压测工具按你算出的进程数乘以 1.5 的并发量打一轮,看 FPM 日志里还会不会冒出 max_children,同时盯着内存别让它逼近上限。两项都过关,这个配置才算站得住。

相关文章

标签:

A5创业网 版权所有