问题

Linux服务器搭建了一个PHP项目,结果内存一直在飙升,进程池里看不到异常,有没有大神帮忙解决下?

回答
哥们,服务器内存飙升这事儿可太折磨人了,尤其是在 PHP 项目上。进程池里没异常,那就说明不是单个 PHP 进程直接撑爆了,得往其他方向挖了。别急,咱一步步来,把这事儿捋清楚。

首先,别慌,咱们得先冷静分析。

内存飙升不是一蹴而就的事儿,通常都有个原因。进程池里看不到异常,说明可能不是 PHP 代码里的内存泄漏(虽然也不能完全排除,只是没那么直接)。咱们要考虑的是:

1. PHP 之外的进程: 操作系统本身、Web 服务器(Nginx/Apache)、数据库(MySQL/PostgreSQL)、缓存(Redis/Memcached)、后台任务、甚至系统服务,哪个占了内存?
2. PHP 进程的“隐形”消耗: 即使单个 PHP 进程没爆,是不是因为进程数量太多,或者每个进程都有些“隐形”的内存开销,累积起来就高了?
3. 外部因素: 比如高并发请求,虽然单个请求处理完了,但如果处理过程中消耗了大量临时内存,然后短时间内大量请求涌入,内存就上去了。
4. 配置问题: Web 服务器、PHPFPM、数据库的配置是否合理?有没有什么限制被突破了?

好了,咱们来动手排查:

第一步:全局视角看内存占用 `top` / `htop` 组合拳

这是最基础也是最重要的。SSH 登录到服务器,先来一发 `top` 或者 `htop` (如果没装 `htop`,`sudo apt install htop` 或 `sudo yum install htop`)。

重点关注:

%MEM 列: 哪个进程占的内存比例最高?
RES (Resident Memory Size) 或 MEM%: 这是进程实际占用的物理内存。
VIRT (Virtual Memory Size): 虚拟内存,不一定全是物理内存,但过高也可能说明问题。
COMMAND 列: 进程的名称。

操作:

1. 在 `top` 或 `htop` 里,按 `M` (大写) 键,让进程按内存使用量排序。
2. 观察:
phpfpm / php 进程: 如果 `phpfpm` 或 `php` 进程本身占用比例非常高,并且数量很多,那就要怀疑 PHP 进程的内存消耗了。
Web 服务器(nginx, apache2, httpd): 看看 Web 服务器本身占多少内存。
数据库(mysqld, postgresql): 数据库是内存大户,尤其如果缓存配置很高。
缓存(redisserver, memcached): 它们设计出来就是吃内存的,但异常飙升就得查原因。
其他进程: 有没有一些不认识的进程?或者一些后台任务(cron jobs)?

初步判断:
如果 `phpfpm` 占了绝大多数内存,且数量很多,那问题很可能出在 PHP 项目本身。
如果 `mysqld` 占了很多,可能是数据库查询慢、缓存配置不当。
如果 `redis` 占了很多,可能是数据量过大、配置问题。
如果是其他未知进程,需要进一步排查。

第二步:深挖 PHPFPM 进程的内存消耗

既然你说进程池里没看到异常,咱们得更细致地看看 PHPFPM。

1. PHPFPM 进程数量和内存:

`ps aux | grep phpfpm`:看看有多少个 `phpfpm` 进程。
`ps aux | grep phpfpm | awk '{print $6}' | awk '{total+=$1} END {print total/1024 " MB"}'`:粗略计算一下所有 `phpfpm` 进程的内存占用总和(以 MB 为单位)。

2. PHPFPM 配置审查(`phpfpm.conf` 或 `phpfpm.d/.conf`):

这个是关键。PHPFPM 的工作模式(`pm`)直接影响进程数量和内存。

`pm = static`: 固定数量的进程。如果设置的 `pm.max_children` 太高,即使单个进程不爆,总量也会很高。
`pm = dynamic`: 动态调整进程数量。
`pm.max_children`:允许同时存在的最大 PHPFPM 进程数。
`pm.start_servers`:启动时的进程数。
`pm.min_spare_servers`:空闲进程最少数量。
`pm.max_spare_servers`:空闲进程最多数量。
`pm.max_requests`:每个子进程服务多少请求后被重启。这是防止内存泄漏累积的利器!

排查思路:

`pm.max_children` 设置得是否过高? 如果你的服务器内存有限,但 `max_children` 设置得很大,即使每个 PHP 进程只占用几十 MB,几百个进程加起来也能轻松压垮服务器。
`pm.max_requests` 设置得是否合理? 如果这个值设置得很高(比如 0 或一个很大的数),那么即使 PHP 代码有缓慢的内存泄漏,进程也不会被重启,泄漏就会持续累积。强烈建议设置一个合理的 `pm.max_requests`,比如 100 或 500。
`pm = dynamic` 下的参数是否均衡? 如果 `max_spare_servers` 设得太高,即使没有太多请求,也会有很多空闲进程驻留内存。

如何修改配置:
找到你的 `phpfpm.conf`(可能在 `/etc/php/X.Y/fpm/phpfpm.conf` 或 `/etc/phpfpm.conf`)以及 `phpfpm.d` 目录下的配置文件,修改 `pm.max_children` 和 `pm.max_requests`。修改后,需要重启 PHPFPM 服务:`sudo systemctl restart phpfpm` (或者 `sudo service phpfpm restart`)。

第三步:定位 PHP 项目内部的内存问题(如果 PHPFPM 占用高)

如果 `top` 显示 `phpfpm` 进程总占用很高,那就要深入到项目代码里找找了。

1. Xdebug + Profiling Tools:

这是最专业的做法。
安装 Xdebug: 确保你的 PHP 环境安装了 Xdebug 扩展。
配置 Xdebug: 在 `php.ini` 中开启 profiling 功能。
```ini
zend_extension=xdebug.so
xdebug.mode = profile
xdebug.output_dir = "/tmp/xdebug_profiles" 设置一个允许写入的目录
xdebug.profiler_enable_trigger=1 通过请求参数触发
```
触发 profiling: 访问你的项目 URL,并在 URL 后面加上 `?XDEBUG_PROFILE=1`,例如 `http://yoursite.com/index.php?XDEBUG_PROFILE=1`。
分析生成的 `.prof` 文件: Xdebug 会在 `output_dir` 生成 profiling 文件。你可以使用 KCacheGrind (Linux/macOS) 或 WinCacheGrind (Windows) 等工具来打开这些文件,它们能直观地显示每个函数占用的 CPU 和内存(在 Xdebug 3.1+ 版本中,profiling 模式已移至 `xdebug.mode=profile`,通常也包含内存信息)。重点关注那些占用内存比例很高的函数。

2. 高内存消耗的常见 PHP 代码模式:

大量数据加载到内存:
一次性读取整个大文件(如 CSV, JSON)到字符串或数组。
执行复杂的 SQL 查询,返回大量结果集,然后直接塞到 PHP 数组里。
使用 ORM 时,一次性加载了太多关联数据。
循环中内存分配: 在一个大型循环中,如果每次循环都分配大量内存,并且没有及时释放,累积起来就会很高。
内存泄漏(虽然在 PHP 中相对少见,但也有可能):
全局变量或静态变量持有了大量不再需要的对象引用。
闭包(closure)捕获了过多的变量,尤其是对象。
某些第三方库的 Bug。
图片处理/文件处理: 如果你的项目涉及到图片缩放、水印、文件合并等,这些操作往往需要大量内存。

如何定位?
代码审查: 重点审查那些处理大量数据、进行复杂计算、或有循环的函数。
日志记录: 在关键函数前后记录内存使用量 (`memory_get_usage()` 和 `memory_get_peak_usage()`)。
```php
// 在函数开始处
$start_mem = memory_get_usage();
error_log("Before processing: " . ($start_mem / 1024) . " KB");

// ... 执行你的代码 ...

// 在函数结束处
$end_mem = memory_get_usage();
$peak_mem = memory_get_peak_usage();
error_log("After processing: " . ($end_mem / 1024) . " KB, Peak: " . ($peak_mem / 1024) . " KB");
```
将 `error_log` 的输出配置到 `phpfpm` 的错误日志中,然后实时查看。

第四步:检查数据库和缓存(如果它们是内存大户)

如果 `top` 显示 `mysqld` 或 `redisserver` 占用了大量内存:

1. MySQL/MariaDB:

慢查询日志: 开启慢查询日志,查找执行时间长、扫描行数多的查询。这些查询可能在内部消耗大量内存。
查询优化: 优化慢查询,添加合适的索引。
缓存配置: 检查 `my.cnf` 中的 `innodb_buffer_pool_size`、`key_buffer_size` 等参数。它们应该根据服务器总内存来配置,不要超过服务器总内存的 5070%,并且要留给操作系统和其他进程。
进程连接: 检查是否有大量不活跃的数据库连接。

2. Redis:

数据量: 你往 Redis 里存了多少数据?是不是数据量增长太快了?
持久化配置: RDB 和 AOF 的配置是否合理?高频率的 RDB 写入可能会消耗一些内存。
内存使用命令: 登录 Redis CLI,使用 `INFO memory` 命令查看详细的内存使用情况。
缓存策略: 你的数据淘汰策略(如 `allkeyslru`)是否合适?有没有无限制增长的 key?

第五步:Web 服务器(Nginx/Apache)的内存占用

Apache:
`mpm_prefork` 模式下,每个请求一个进程,进程数过多会占用大量内存。
`mpm_worker` 或 `mpm_event` 模式下,每个进程有多个线程,相对节省内存,但也要看线程配置。
检查 Apache 的配置,比如 `MaxRequestWorkers` (对应 `MaxClients`),这个值太高也会导致进程过多。
Nginx:
Nginx 本身是事件驱动的,单个进程占用内存较少。
问题可能出在 Nginx 的 worker 进程数量 (`worker_processes`),或者大量的 FastCGI/uWSGI/PHPFPM 子进程(这些是 PHPFPM 进程,不是 Nginx 自己的)。
检查 Nginx 的 `proxy_buffers` 和 `proxy_buffer_size` 配置,虽然一般不会直接导致内存飙升,但过高的设置可能对处理大量长连接时有影响。

第六步:系统级监控和分析

`vmstat`: 周期性输出系统内存、交换、IO 等信息。
`vmstat 5`:每 5 秒输出一次。关注 `si` (swap in) 和 `so` (swap out) 列,如果它们持续为非零,说明系统内存不足,正在频繁使用交换空间。
`sar`: 系统活动报告工具,可以查看历史的资源使用情况。
`sar r 5 10`:每 5 秒采样一次,共采样 10 次,显示内存使用情况。
`dmesg`: 查看内核日志,有时内存问题(如 OOM Killer 杀进程)会在这里留下痕迹。

总结和排查流程建议:

1. 先用 `top`/`htop` 确定是哪个进程在消耗内存。
2. 如果是 `phpfpm`,重点检查 PHPFPM 的配置 (`pm.max_children`, `pm.max_requests`)。把 `pm.max_requests` 设置成一个相对小的值(如 100500)是快速止损的办法。
3. 如果 PHPFPM 的进程数量正常,但每个进程内存很大,或者 `pm.max_requests` 已经设了,内存还是飙升,那大概率是 PHP 项目代码本身的问题。 使用 Xdebug profiling 或者日志记录来定位。
4. 如果发现是数据库或缓存占用的内存,就去优化它们的配置和查询。
5. 如果发现是 Web 服务器(尤其是 Apache prefork 模式),调整其并发连接数相关的配置。

一些“土办法”:

重启服务: 简单粗暴,但能暂时缓解。
`sudo systemctl restart phpfpm`
`sudo systemctl restart nginx` (或 `apache2`/`httpd`)
`sudo systemctl restart mysql` (或 `postgresql`/`redisserver`)
重启后再观察内存变化。
逐个排查: 如果不是 PHPFPM,就关闭其他非核心服务(如 Redis、MySQL),看内存是否下降,以此缩小范围。

重要提示:
在修改任何配置文件之前,务必备份!
排查问题是一个反复试验的过程,修改一个配置,观察一段时间,再决定下一步。

兄弟,你这个情况,我个人倾向于先从 PHPFPM 的 `pm.max_requests` 入手。这个设置不当是导致 PHP 进程内存缓慢增长最终爆仓的常见原因。如果改了之后问题还在,再考虑代码层面的 profiling。

把你的服务器配置(CPU核数、总内存大小)和 PHPFPM 的配置(`pm` 类型、`max_children`、`max_requests`)发上来,大家或许能给你更具体的建议。祝你好运!

网友意见

user avatar

一个php-fpm就是一个php的进程,响应一个http请求。

应该是你的php程序没写好,导致占用了超大内存且用时较长。

一般是由于处理了超大数组造成的,例如从mysql里读一个10万行记录的表,你把表里的每个条记录都放到了php的数组里。

类似的话题

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有