
先说结论:按内存算,别按感觉调
pm.max_children 是 PHP-FPM 池能同时处理的请求数上限。给太小 → 高峰期报错、请求排队甚至 502;给太大 → 内存吃光触发 OOM Kill,整台机器上的站一起挂。
正确算法就一句话:单个 PHP 进程的实际内存 × max_children < 可用内存,同时给系统和缓存留出余量。
一、报错长什么样
php-fpm.log 里会出现这样一行:
WARNING: [pool www] server reached pm.max_children setting (8), consider raising it
它不代表已经挂了,而是说明已经顶到上限,请求开始排队——对 WordPress 来说,下一步就是响应变慢、然后 502。
二、先量一个 PHP 进程到底吃多少
不要拿 ps 的 RSS 直接相加(共享内存会被重复计算),看 PSS:
for p in $(pgrep -f 'php-fpm: pool www'); do
awk '/^Pss:/{s+=$2} END{print s}' /proc/$p/smaps_rollup 2>/dev/null
done | sort -n | awk '{a[NR]=$1; s+=$1} END{print "最小",a[1],"最大",a[NR],"平均",int(s/NR)}'
我实测一组 WordPress worker:私有内存 11~33 MB,PSS 40~57 MB(Redis 扩展开着、opcache 开着、主题不算重)。
注意区分两个数:私有内存(
Private_Dirty,真正独占的)和 PSS(把共享内存按比例摊回来)。ps的 RSS 会把 opcache、扩展这些共享页在每个进程里各算一遍,所以用它估总量会偏大。取偏保守的 55 MB 来算上限即可。
三、再算可用内存:要看 MemAvailable
free -m
grep -E 'MemAvailable|SwapTotal' /proc/meminfo
关键是 MemAvailable,不是 free 那一列。我调优时实测 ≈ 2.4 GB(这是一台 4 GB 的机器,系统和面板已经先占掉一部分):
2400 MB ÷ 55 MB ≈ 43
理论上能开到 43,但不要按理论值给满——还要留 Redis、nginx、系统进程和突发峰值的余量。
我实际的调整过程是这样两步:
| 时间 | max_children | 依据 |
|---|---|---|
| 最初 | 8 | 面板默认,两次触发 reached pm.max_children setting (8) |
| 第一次 | 20 | 20 × 55 MB ≈ 1.1 GB,留出约 1.3 GB 余量 |
| 第二次 | 24 | 实测打满时是 6~8 请求/秒 × 2~3 秒/请求 ≈ 20 并发,小幅上调 |
第二次没有直接跳到 28:因为站群里会用 Imagick 处理大图,单个请求就可能吃掉几百 MB,留点余量比多开几个 worker 划算。
四、完整参数
pm = dynamic
pm.max_children = 24
pm.start_servers = 6
pm.min_spare_servers = 4
pm.max_spare_servers = 8
pm.max_requests = 500
request_terminate_timeout = 120
request_slowlog_timeout = 5
slowlog = /www/server/php/82/var/log/slow.log
listen.backlog = 8192
pm.max_requests = 500:worker 处理 500 个请求后自我重启,缓解慢性内存泄漏;request_terminate_timeout:单请求超 120 秒直接杀掉,避免 worker 被卡死;request_slowlog_timeout = 5+slowlog:超过 5 秒的请求会记录 PHP 调用栈,是抓"到底哪个插件慢"最有效的工具,别关。
五、一个容易踩的坑:池里的 memory_limit 覆盖
我一开始在 FPM 池里单独写了 php_admin_value[memory_limit] = 256M,结果用 Imagick 处理大图时反复报:
Allowed memory size of 268435456 bytes exhausted
原因是池里的覆盖会盖掉 php.ini。后来把这行删掉,统一用 php.ini 的 1024M,内存风险改由 max_children 控制——因为真正决定内存占用的是「进程数 × 单进程」,不是把单进程限额调大就会用满。
六、改完怎么验证
# reload(宝塔下服务名以实际为准)
systemctl reload php-fpm-82
# 之后不再出现 reached
grep -c 'reached pm.max_children' /www/server/php/82/var/log/php-fpm.log
# 看实时并发
pgrep -c -f 'php-fpm: pool www'
# 重点看 swap 有没有继续涨(快涨说明给多了,该降回去)
swapon --show
调整之后要盯两个信号:reached 告警是否消失、swap 的 used 是否继续增长。前者还出现说明给少了,后者持续上涨说明给多了。
七、回滚
把 pm.max_children / pm.start_servers / pm.min_spare_servers / pm.max_spare_servers 改回原值(这条从 20 调到 24,回滚就是改回 20)再 reload。
一句话总结:max_children 不是越大越好,用「PSS × 并发数 < MemAvailable」来算;调之前先量 PSS,调之后盯 reached 告警和 swap 增长,并且把 slowlog 打开——它比任何猜测都更快指出真正的瓶颈。

评论 (0)