PHP-FPM日志报server reached pm.max_children setting的排查与解决方法(pm.max_children计算与slowlog定位慢请求)

站点白天跑着跑着开始抽风,一半请求正常、一半返回502,过几分钟自己又好。登上机器看CPU不高、内存也没满,nginx错误日志里刷的是同一句:connect() to unix:/run/php/php8.2-fpm.sock failed (11: Resource temporarily unavailable)。再翻php-fpm自己的日志,WARNING: [pool www] server reached pm.max_children setting (20), consider raising it。

这两条说的是同一件事:worker被占满了,新请求排不进队,nginx等不到响应就返回502。

先把现象和数值对上。pm.max_children是进程池上限,一个worker同一时刻只处理一个请求。上限20、单请求平均耗时2秒,这台机器的并发天花板就是每秒10个请求;流量摸到每秒30个,剩下20个只能排队,队排满了nginx那边就是502或者504。所以看到这条WARNING,第一反应不该是加内存,而是先问一句请求为什么变慢了。

确认是不是worker不够

先开状态页,让php-fpm自己把池子里的情况吐出来。pool配置里加一行:

pm.status_path = /fpm-status

nginx对应的server块里配一段,只允许本机访问:

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

然后本机curl一下:

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

返回里几个字段要盯着看:listen queue大于0说明请求已经在排队,active processes贴着max children说明池子跑满,max children reached后面那个计数还在涨,那就坐实了。PHP 7.4、8.0、8.1、8.2、8.3、8.4,这几个版本的输出字段基本一致。现场排查时这个页面比看日志直观,排队数是0还是几十,一眼就分得清是池子小还是请求慢。

算max_children,别拍脑袋填

往上调之前先算一笔账,不然容易把内存撑爆,最后被内核杀掉。先量单个worker实际吃多少:

ps --no-headers -o "rss,cmd" -C php-fpm8.2 | awk '{sum+=$1} END {print sum/NR/1024 "MB"}'

假设这条出来是60MB,机器8G内存,留2G给系统和数据库,剩4G给php,上限大概在4×1024÷60≈68,取个整数60就行。别贴着算出来的数填,内存碎片和峰值请求都不在平均值里。也别一次拉满,先加一半观察半小时。

改的是pool配置文件,Ubuntu和Debian在/etc/php/8.2/fpm/pool.d/www.conf,CentOS和Rocky Linux在/etc/php-fpm.d/www.conf:

pm = dynamic
pm.max_children = 60
pm.start_servers = 8
pm.min_spare_servers = 4
pm.max_spare_servers = 16
pm.max_requests = 500

pm.max_requests这行别删。它让worker处理满500个请求后自己退出重开,用来回收慢慢泄漏的内存;删掉它,跑几天之后单个worker能涨到几百MB,最后同样是被系统干掉。

改完先查语法再reload。reload是平滑重启,不会断掉正在处理的请求,restart会:

php-fpm8.2 -t
systemctl reload php8.2-fpm

池子跑满多数是请求变慢了

池子突然不够用,背后通常是一条没走索引的SQL、一个没设超时时限的外部API调用、或者一次没加缓存的大报表导出。开慢日志把它们抓出来:

slowlog = /var/log/php-fpm/www-slow.log
request_slowlog_timeout = 5s

然后看日志里的调用栈:

tail -n 50 /var/log/php-fpm/www-slow.log

慢日志会打出卡住的文件和行号,顺着查下去多半落在数据库。之前碰到过一次,某个列表页因为漏了个复合索引,单请求从200ms涨到9s,20个worker瞬间打满;索引补上之后pm.max_children压根没动,502自己就消失了。

另外三处要一起看

如果php-fpm进程直接没了、日志里干干净净连max_children都没有,那要看内核有没有出手:

dmesg -T | grep -i "killed process"
journalctl -k | grep -i oom

看到Out of memory: Killed process (php-fpm8.2)这种行,说明max_children填大了或者代码在漏内存,加内存只是把爆掉的时间往后推。第二种常见情况是socket路径或权限对不上,nginx日志会写No such file or directory或者Permission denied,那跟worker数量无关,是listen.owner和nginx的运行用户没对上。第三种是队列太短,listen.backlog默认511,跟net.core.somaxconn一起调大:

sysctl -w net.core.somaxconn=4096

顺手写进/etc/sysctl.conf再sysctl -p,不然重启之后又回到原样。

改完别立刻走人,挂着看两分钟:

watch -n 1 'ps aux | grep php-fpm | grep -v grep | wc -l'
tail -f /var/log/nginx/error.log

两个数字都稳住、error.log不再刷502,才算收工。Ubuntu 22.04、24.04上这套路径都一样,CentOS 7那批机器的日志在/var/log/php-fpm/error.log,别找错地方。以上命令与默认参数基于PHP 8.2,数据截至2026年9月。