发布时间:2026-09-27 12:30 更新时间:2026-09-27 12:30 阅读量:0
有站长碰到过这种怪事:服务器内存明明还剩一大半,free -m看着很宽裕,网站却隔一阵子就蹦出502 Bad Gateway,刷新几下又自己好了。到宝塔面板里看PHP-FPM状态,进程数忽高忽低,错误日志里夹杂着 server reached pm.max_children setting 或者 seems busy 之类的提示。这类问题的根子,往往不在内存够不够,而在子进程被回收、重建的那一瞬间,请求没有进程可接。
本文围绕PHP-FPM的进程回收机制,讲清 pm.max_requests 到底在做什么、它和 pm.max_children 是什么关系,以及参数怎么取舍才能既控制内存占用又不制造502。文中的命令与配置以通用PHP-FPM为准,宝塔面板的路径会单独说明,具体以你服务器上的实际版本和官方文档为准。
PHP-FPM是master-worker模型。master进程负责管理,真正处理PHP请求的是worker子进程。Nginx把请求通过FastCGI交给PHP-FPM,如果此刻进程池里没有空闲worker,请求要么排队,要么直接被拒,前端表现就是502或504。
pm.max_requests 这个参数的含义是:每个worker子进程处理完指定数量的请求后,就主动退出,由master再拉起一个新的顶上。它最初的用途是规避第三方扩展潜在的内存泄漏——进程处理得越多,占用可能越涨,定期换新进程能把内存收回来。
问题就出在这个“退出—重启”的间隙。当一批worker几乎同时到达请求上限同时退出,而master重建进程需要时间,短时间内容量骤降,请求就可能拿不到worker。如果站点并发不高、进程池留有余量,这个间隙感知不到;一旦流量偏集中,或者 max_requests 设得很小,502就会周期性出现。所以内存充足却频繁502,先别急着加内存,要看进程回收节奏。
先确认现象,看进程池状态和错误日志:
# 查看 PHP-FPM 进程池实时状态(需在 pool 配置中开启 status_path)
curl http://127.0.0.1/phpfpm_status
观察 worker 数量变化与重启情况
ps -eo pid,ppid,etime,cmd | grep 'php-fpm: pool'
查看错误日志里的关键提示(路径以实际环境为准)
tail -f /www/server/php/80/var/log/php-fpm.log
如果 etime 显示大量worker的运行时间都很短、且集中在同一时段,基本可以判断是回收过于频繁。
这两个参数要放在一起看。pm.max_children 决定进程池上限,直接影响内存峰值;pm.max_requests 决定单个进程的“寿命”。常见错误有两种:一是把 max_requests 设成很小的值(比如100、200),进程频繁换代;二是只调大 max_children 却不看单进程内存,结果内存被吃满触发OOM,反而更糟。
合理的思路是:先量出单个PHP-FPM进程的常驻内存,再结合可用内存反推 max_children。可以用下面这条命令粗估平均占用(单位KB),实际值因框架和扩展差异很大:
ps --no-headers -o rss -C php-fpm | awk '{sum+=$1; n++} END {print "平均RSS(KB):", sum/n, "进程数:", n}'
至于 pm.max_requests,如果站点运行的代码和扩展没有明显内存泄漏,把它设得偏大一些更稳,例如1000到5000之间,甚至设为0表示不主动回收。是否要回收、回收阈值多少,建议以压测和监控数据为准,而不是照搬某个固定数字。判断有没有泄漏,可以观察进程RSS是否随请求量持续上涨:
# 连续采样,观察 worker 内存是否单调增长
while true; do date; ps --no-headers -o rss -C php-fpm | awk '{s+=$1} END {print "RSS合计(KB):", s}'; sleep 30; done
如果合计值长期平稳,就没必要让进程频繁换代;如果确实缓慢上涨,可以适度回收,但阈值别太低。
另外,进程池的调度模式也影响回收体验。pm = dynamic 下,空闲进程会被杀掉,配合较小的 max_requests,重建更频繁;流量稳定的站点用 pm = static 可以让worker常驻,减少换代开销,代价是内存占用更“实”。这个选择要结合内存余量来定。
改配置之前先备份。宝塔面板的PHP-FPM进程池配置一般在 /www/server/php/版本/etc/php-fpm.d/ 下(以实际安装路径为准),手动编译安装的常见路径是 /usr/local/php/etc/php-fpm.d/www.conf。
# 备份当前进程池配置
cp /www/server/php/80/etc/php-fpm.d/www.conf \
/www/server/php/80/etc/php-fpm.d/www.conf.bak.$(date +%F)
编辑关键参数
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 2000
改完不要用 kill -9 硬重启,否则正在处理的请求会断。优先用平滑重载,让master带着新配置重建worker:
# 先测试配置语法(路径以实际环境为准)
/www/server/php/80/sbin/php-fpm -t
平滑重载,不中断在途请求
kill -USR2 $(cat /www/server/php/80/var/run/php-fpm.pid)
确认 worker 已按新配置拉起
ps -eo pid,etime,cmd | grep 'php-fpm: pool'
重载后持续观察一段时间:进程数是否稳定在预期区间、错误日志里 max_children 告警是否消失、502是否还周期性出现。若调整后并发依然吃紧,说明瓶颈可能在数据库或上游脚本执行时间,而不是进程数,需要换方向排查。
最后提醒两点。一是如果前面挂了Nginx反向代理或CDN,502的来源要分清:Nginx错误日志里 connect() to unix:/tmp/php-cgi.sock failed 指向PHP-FPM,而 upstream timed out 更可能是后端响应慢。二是所有参数改动都建议在低峰期做,并保留回滚文件,改动幅度一次只动一两个值,方便定位影响。
小结一下:内存充足却频繁502,多半是worker在回收重建时短暂缺位。先看进程运行时间和内存趋势,判断是回收过频还是确有泄漏,再决定 pm.max_requests 取大还是取小,同时用 pm.max_children 卡住内存上限。参数没有万能值,按自己站点的流量曲线和实测数据来调,比抄一个数字靠谱得多。
| 📑 | 📅 |
|---|---|
| Nginx泛域名证书自动签发:acme.sh DNS验证与泛解析站点批量部署 | 2026-09-27 |
| WordPress 数据库表前缀修改实战:从 wp-config 到 SQL 批量改名 | 2026-09-27 |
| 宝塔面板日志被CDN节点IP填满:log_format与real_ip联动调整 | 2026-09-27 |
| Nginx resolver 域名解析缓存:反代上游换 IP 后仍走旧地址的排查 | 2026-09-27 |
| MySQL被OOM Kill排查:swap、buffer pool与监控取舍 | 2026-09-27 |
| HTTPS混合内容批量修复:控制台与curl定位http资源 | 2026-09-27 |
| 宝塔面板部署 WordPress 后固定链接 404:伪静态规则加载顺序与 try_files 排查 | 2026-09-27 |
| 网站301与302混用导致权重分散:跳转类型选择与生效验证 | 2026-09-27 |
| WordPress后台被暴力撞库告警:登录限速与日志加固 | 2026-09-26 |
| 域名下PC站与m站SEO冲突:canonical、自适应与UA跳转取舍 | 2026-09-26 |