CentOS 下 PHP-FPM 内存占用高的排查与优化
一、快速定位占用来源
- 查看总体内存与交换分区:free -m,确认是否因内存不足触发频繁换页或被 OOM Killer 终止进程。
- 按内存排序定位进程:top 后按 Shift+M,或 ps -eo pid,ppid,cmd,%mem,rss --sort=-%mem | head,找出占用最高的 php-fpm 进程。
- 统计 PHP-FPM 进程数量:pstree | grep php-fpm,或 ps aux | grep php-fpm | wc -l,判断是否“进程过多”。
- 查看单个进程常驻内存(RSS,单位 KB):ps -o pid,rss,cmd -C php-fpm,用于估算每个子进程的内存占用。
- 检查 PHP-FPM 日志与慢日志(如 /var/log/php-fpm.log、www.log 的 slow 段),定位是否有异常请求、死循环或慢 SQL。
- 若使用 Nginx,核对 fastcgi 超时与缓冲设置,避免上游阻塞导致进程长时间占用。
以上方法可快速判断是“进程数量过多”还是“单进程内存泄漏/膨胀”,为后续优化提供依据。
二、核心优化步骤
- 调整进程管理模式与数量
- 小内存或波动负载:优先用 dynamic,按需伸缩,节省内存。
- 大内存且追求稳定:可用 static,减少进程频繁创建销毁的开销。
- 极小内存或低并发:可用 ondemand,有请求再 fork,但首次响应可能略慢。
- 计算并收紧 pm.max_children
- 估算公式:max_children ≈ 可用内存 / 单个子进程常驻内存(RSS)。
- 经验值:在 1GB 内存的 VPS 上,通常 10–20 个进程较稳妥;实际需以你的 PHP 扩展和业务代码为准。
- 动态模式的配套参数
- 建议将 pm.max_spare_servers 设为 pm.max_children 的 60%–80%,兼顾突发与回收。
- 示例(以 1GB 内存、单进程 RSS 约 50MB 估算):max_children ≈ 1000/50 ≈ 20;max_spare_servers ≈ 12–16。
- 控制生命周期与泄漏风险
- 设置 pm.max_requests(如 500–5000),定期回收子进程,释放潜在内存碎片与泄漏。
- 限制脚本内存与执行边界
- 在 pool 配置中设置 php_admin_value[memory_limit](如 32M/64M/128M),避免单个脚本耗尽内存。
- 生效与回滚
- 修改后先 systemctl reload php-fpm 观察;稳定再重启。变更前务必备份配置。
这些做法能同时解决“进程过多”和“单进程吃内存”两类根因,是见效最快的组合拳。
三、配置示例
[www]
pm = dynamic
pm.max_children = 20
pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 12
pm.max_requests = 1000
php_admin_value[memory_limit] = 64M
- 示例 B(稳定,适合 8C/16GB 内存、静态模式)
[www]
pm = static
pm.max_children = 20
pm.max_requests = 2000
php_admin_value[memory_limit] = 128M
说明:示例 A 的 max_spare_servers 取了 max_children 的 60%(12);示例 B 采用 static 固定进程数,适合高并发且内存充裕的场景。数值需结合你的 RSS 实测与业务峰值微调。
四、应用侧优化与长期治理
- 优化代码与依赖:减少重型扩展(如 imagick、xhprof、grpc)的全局加载;仅在需要的路由初始化;避免在请求中反复加载大文件。
- 缓存与分页:启用 OPcache(生产建议开启),对列表/搜索做分页与缓存,降低单次请求内存与时长。
- 队列化解耦:将耗时任务(邮件、导入、图片处理)丢入 消息队列/后台任务,避免阻塞 FPM 进程。
- 数据库与 I/O:优化慢查询、加索引、批量提交;限制单次请求处理的数据量。
- 监控与告警:持续观察 RSS、进程数、max_requests 回收次数、慢日志;设置内存阈值告警,及时回滚异常版本。
- 变更流程:先在测试环境压测(如 ab/wrk/siege),记录峰值 RSS 与 95 分位响应时间,再推广到生产。
通过“代码减负 + 缓存 + 异步 + 限流”的组合,能从根本上降低每个请求的峰值内存与并发压力。