猛男冲冲冲苹果版
214.5M · 2025-09-18
理解 PHP-FPM 请求流程、进程池大小调整,以及防止超时和 502 错误的关键设置 — 实用规则、实际案例和可直接使用的检查清单。
大多数 PHP 应用出问题,不是因为 Nginx,而是 PHP-FPM(FastCGI 进程管理器)没配好或者进程数设置有问题。结果就是:p95 延迟突然飙高、502 错误随机出现,还有你看不到的请求排队。这篇文章会告诉你请求在 FPM 里是怎么跑的、进程池怎么工作,以及哪些配置能让应用不卡顿 — 这样流量突然上来时,你的应用还能正常响应。
原文链接-PHP-FPM 深度调优指南 告别 502 错误,让你的 PHP 应用飞起来
pm.max_children
的靠谱方法CGI(通用网关接口):旧的模型,Web 服务器为每个请求生成一个进程。简单但很慢。
FastCGI:Web 服务器和应用管理器之间的长连接协议。服务器复用连接,不用每次都新建进程。
PHP-FPM(FastCGI 进程管理器):管理 PHP 工作进程池的后台程序。每个进程池有自己的套接字、用户和配置。Nginx/Apache 把请求扔给套接字,FPM 负责分配给空闲的工作进程,或者排队等待。
进程池(Pool):一组共享配置的工作进程,通常按应用、租户或权限来划分。进程池能隔离故障和资源占用。
工作进程(Worker/Child):执行 PHP 脚本的解释器进程,一次只能处理一个请求。子进程内部不支持并发,每个请求都得有个空闲的子进程来处理。
OPcache:把编译好的 PHP 字节码缓存在共享内存里。每个工作进程都能用,大幅降低每个请求的 CPU 开销。
https://example.com/index.php
.php
文件,转发给 FastCGI,比如:fastcgi_pass unix:/run/php/php-fpm.sock;
容易卡住的地方:
大多数人设置 pm.max_children
时会想"我们有几个核?"这样想是错的。每个 PHP 工作进程可能要吃掉几十甚至几百 MB 内存,具体看你的代码、扩展和 OPcache。如果开太多工作进程,内核开始换页或者 OOM killer 直接干掉进程 — 不管哪种情况,延迟都会爆炸。
正确的做法:
max_children = floor(进程池内存预算 / 每个子进程RSS)
pm.status_path
验证效果dynamic
;特别稳定的负载用 static
;流量不稳定、访问量小的机器用 ondemand
,这时候省内存比避免冷启动更重要拿这个当模板,根据你的系统调整路径和名称。
; /etc/php-fpm.d/app.conf
[app]
user = www-data
group = www-data
listen = /run/php/php-fpm-app.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
listen.backlog = 1024
pm = dynamic
pm.max_children = 32 ; 按内存算出来的 - 看下面的案例
pm.start_servers = 8
pm.min_spare_servers = 8
pm.max_spare_servers = 16
pm.process_idle_timeout = 20s
pm.max_requests = 2000
; 保护机制
request_terminate_timeout = 30s
request_slowlog_timeout = 2s
slowlog = /var/log/php-fpm/app.slow.log
; 健康检查
pm.status_path = /status
ping.path = /ping
ping.response = pong
在 Nginx 里加个 location 来暴露状态(注意控制访问权限):
location ~ ^/(status|ping)$ {
allow 127.0.0.1;
deny all;
include fastcgi_params;
fastcgi_pass unix:/run/php/php-fpm-app.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
场景:8 GB 内存的虚拟机跑 Laravel 应用。机器上还有 Nginx、Redis 和系统服务,大概要用掉 2 GB。剩下 6 GB 给 PHP-FPM(app 进程池)。
1. 测量每个子进程内存。正常负载下,抽样 50 个工作进程:RSS 在 90-150 MB 之间,中位数 120 MB。
2. 算出 pm.max_children。floor(6,000 MB / 120 MB) = 50
。保险起见,设成 pm.max_children = 44
,给突发流量和 OPcache 碎片留点余量。
3. 应对突发流量。用 dynamic 模式:
pm.start_servers = 12
pm.min_spare_servers = 12
pm.max_spare_servers = 24
这样保持 12-24 个空闲工作进程,短时间的流量突增不用排队。
4. 防止内存泄漏。设置 pm.max_requests = 1500
。观察 RSS 变化,如果工作进程一天内内存涨了 30% 以上,就调到 1000。
5. 验证效果。
如果队列长度 > pm.max_children/4
持续超过一分钟,要么加大 max_children
(内存够的话),要么优化慢接口。
为什么优先考虑内存? 就算你有 16 个 CPU 核心,60 个 PHP 工作进程每个占 150 MB + 共享内存,也可能导致系统抖动。OOM killer 一旦出手,整个分钟的 p95 延迟都完蛋。用内存来控制并发数,避免这种悬崖式的性能下降。
pm.status_path = /status
,限制只能 localhost 或内网访问request_slowlog_timeout = 2s
,slowlog 路径要能写入/ping
给健康检查用常用检查命令:
curl -s http://127.0.0.1/status?full # 查看空闲、活跃、最大子进程数等
tail -f /var/log/php-fpm/app.slow.log # 负载测试时实时看慢日志
在真实负载下,用 ps -o pid,rss,cmd -C php-fpm
或 smem -P php-fpm
采样。
算出中位数 RSS。忽略那些在做文件上传或生成大报告的异常值。
pm = dynamic
;定时任务多、流量小的机器用 ondemand
;只有流量特别稳定且内存摸得很清楚时才用 static
pm.max_children
,比理论最大值低 10-20% 开始start_servers
和备用进程数,让平时的空闲进程数保持在备用范围的下半部分request_terminate_timeout
要和你的实际 SLO 匹配。客户端 30 秒就超时了,别让 PHP 跑 5 分钟fastcgi_read_timeout
要和 PHP 的上限对齐(稍微高一点),避免提前返回 504listen.backlog = 1024
来应对短时间的连接突发max_children
,要么优化代码opcache.memory_consumption
pm = dynamic
,pm.max_children
按内存算出来,备用进程保持基线的 10-50% 空闲max_requests
设在 1,000-3,000,确认 24 小时内没有内存泄漏request_terminate_timeout
符合 SLO 要求,Nginx 超时时间对齐负载测试 → 查看 pm.status
队列和空闲数 → 调整 max_children
→ 再测试。
观察 p95 延迟和错误率,如果队列一直在增长,先通过慢日志和 APM 找出慢接口,优化后再考虑提高并发数。
PHP-FPM 不是什么黑盒子,它就是个小而精确的系统。按内存来算并发数,保持几个预热的备用进程,把超时时间对齐好,像盯着股票一样盯着 OPcache 和队列长度。把进程池当成预算来管理 — 每个子进程都要吃内存 — 别照搬其他技术栈的经验值。每次改代码或调基础设施后,跑个简单的负载测试,先看看状态和慢日志,再决定要不要买更多硬件。坚持这个循环,你的 p95 延迟就会一直很稳定 — 就算流量突然变得很奇怪也不怕。
214.5M · 2025-09-18
319.8M · 2025-09-18
211.2M · 2025-09-18