PHP -fpm 服务器内存占用陡坡式上涨,请问如何彻底解决?

2020-11-19 11:04:56 +08:00
 reyleon
我手上维护着两台 Web 服务器,后端运行的是 PHP 接口服务,服务器配置为 8 核 16G,PHP 版本为 5.6.40 ,运行在 PHP-FPM 模式下,php-fpm 进程的内存使用情况随着请求数的增加而不断上涨,系统可用内存呈陡坡式下降,一不留神就会有内存告警但我们又处理不够及时而导致业务奔溃的可能。

这个问题让我黯然伤神,不知该如何是好?

我查了一下,几乎全网都在告诉你调整 php-fpm 运行模式,如 pm 改为 dynamic 或 static 以及调整 php-fpm 子进程数量,或者就是让你调低 pm.max_requests 的值,让它达到请求数时自动销毁进程以释放内存。

我们就是这么做的,虽然有点用,但这明显不是治本的办法,这只是一种妥协。

让我疑惑的是,对于内存的释放,我也查到不同的说法:

说法一(来自 Swoole 官方微信公众号)

FPM 自带黑魔法,传统的跑在 FPM 下的 PHP 代码是没有“内存泄漏”一说的。因为 PHP 内核有一个关键函数叫做 php_request_shutdown,此函数会在请求结束后,把请求期间申请的所有内存都释放掉,这从根本上杜绝了内存泄漏。


说法二(来自 PHP 开发组核心成员博客)

PHP 之所以会在请求结束后正确的释放掉所有的资源,内存,这是因为当我们在脚本中使用新的内存的时候,PHP 会向 OS 申请一大块内存(ZEND_MM_SEG_SIZE 大小),然后分给你你需要的合适的一块小内存。

当你不使用这块小内存的时候,PHP 也不会返还给 OS,而是保留下来给后续的处理使用。所以,如果你使用完了资源不及时释放,那么后续的逻辑如果请求内存,PHP 发现之前申请的一大块内存已经分光了,它就只好再次向 OS 发起 malloc 调用,得到一块新的大内存。 并且它还需要对这个大内存做一些标记处理。

而如果你使用完资源,及时释放的话,那么下次脚本申请内存的时候,你之前归还的内存块就可以被重复利用,那么也许你的整个脚本只需要和 OS 申请一次内存。

如果你买了一本 PHP 的书,它告诉你: “不用在 PHP 主动释放资源,因为 PHP 会帮你释放”的话,我建议你,烧了它。


说法三(来自 PHP 官方手册)

引用计数系统是 Zend 引擎的一部分,可以自动检测到一个资源不再被引用了(和 Java 一样)。这种情况下此资源使用的所有外部资源都会被垃圾回收系统释放。因此,很少需要手工释放内存。


以上说法,不知道哪个是正确的。但事实是存在的,php-fpm 进程的内存使用就是会上涨。

有没有大神指点一下,如何深入分析内存上涨的原因?假设要深入 PHP 代码,有没有行之有效的分析工具?

(注:我不是 php 程序员)。
5365 次点击
所在节点    程序员
63 条回复
wangritian
2020-11-19 14:01:40 +08:00
max_requests 我觉得并不是妥协,而是针对大量不可控代码的最有效方案
如果你想深究,建议分析业务代码,通过压力测试定位到泄露内存的函数,如果是第三方类引起的,仔细读一下他的文档,是否忘记释放对象或某些关键方法
Evilk
2020-11-19 14:04:14 +08:00
@lbp0200 你的" Reply 3
lbp0200 2 小时 43 分钟前
PHP 就是这样的异步处理模型,一个请求一个进程,1000 个并发就是 1000 个 PHP 进程,请求结束,进程关闭"
看得出来,你对 PHP 的认识,还是比较老旧,建议更新下
fenglangjuxu
2020-11-19 14:04:46 +08:00
我觉得升级 php 到 7 可能比较简单点.当然可能还得修改一些代码不兼容的地方.
debug 工具 可以试下这个 phptrace 360 出的
sunznx
2020-11-19 14:18:05 +08:00
招个 php 的维护不行吗
pigfly123
2020-11-19 14:20:22 +08:00
1. 检查 fpm 配置是否合理,看看处理每个请求占用的内存来设置一个合理的值;
2. 利用 xhprof 去线上开启内存分析采样,基本能定位到内存占用高的具体位置,然后优化代码。
lbp0200
2020-11-19 14:27:15 +08:00
@Evilk 一个免费的解决方案,您还想要多详细?加个进程池,就自行车变特斯拉了?
reyleon
2020-11-19 14:27:34 +08:00
@liuxu 还是不能理解 php-fpm 进程内存使用会慢慢上涨的原因。

最初启动时一个 php-fpm 进程占用大概 20M 内存,随着接受请求数的增加,内存慢慢会往上涨到 120M 左右,这个时候达到了我们设置 pm.max_requests 的值,然后进程销毁,如何周而复始。

如你所说 “引用计数释放掉的内存和 php_request_shutdown 释放掉的内存都是还给 zend,zend 不还给系统”。

那我这个内存一直往上涨,说明是 Zend 一直在向 OS 申请内存,这没错吧。
那这是不是可以说 “引用计数” 和 “php_request_shutdown” 根本就没有释放过内存?因为如果释放了内存给 Zend,那就说明 Zend 手里有空闲内存,那就不用向操作系统申请啦。

另:我现在正在研究如何使用 xhprof 分析业务代码。
我也是操碎了心。
reyleon
2020-11-19 14:30:52 +08:00
@wangritian 可能如你所说,因为同样的 php 环境,只是不同业务的另外一台服务器就没有出现过内存上涨的情况,内存使用稳如狗。
reyleon
2020-11-19 14:32:38 +08:00
@pigfly123 第一步的配置应该是没问题的。我现在在研究使用 xhprof 去分析代码,希望能有结果
reyleon
2020-11-19 14:34:48 +08:00
@sunznx 并不是没有 php 人员呀,之前写这套代码的人已经离职了;
不过不是我说,就算是自己写的代码,也不一定能轻易找出原因吧,这个问题在他在职的时候也不是没有提过
然而没卵用
C603H6r18Q1mSP9N
2020-11-19 14:35:54 +08:00
php-fpm 我们接触下来 无解!!!
并发高了,会吃内存和数据库链接不释放,导致整套系统垮掉,解决是高并发 上 java
reyleon
2020-11-19 14:42:51 +08:00
@Evilk 一个请求对应一个 PHP 进程,请求处理结束,这个进程才能处理下一个请求。1000 个并发如果没有那么多 php 进程处理,那么那些请求就在队列里排队等待处理。

我也是这么理解的。这有问题吗?需要更新对 PHP 哪方面的认识?
liuxu
2020-11-19 14:48:32 +08:00
@reyleon 没错,你得看看你的进程是不是结束从新起了新的
lovecy
2020-11-19 14:49:12 +08:00
@reyleon 一个 php-fpm 进程就是一个 cgi 进程咯,也就是对应一个 zend 。那些第三方库、扩展啥的,都是自己的内存池吧。如果调用一次 php 代码,运行一次扩展,扩展申请一次 zend 的内存而不释放,代码调用次数增加,慢慢 zend 内存就不够用了。这时候销毁 cgi 进程重启,就能释放掉这些内存。

如果没法改代码,就按 19 楼的说法调优 fpm 咯,反正能用就行,#21 楼说的"max_requests 我觉得并不是妥协,而是针对大量不可控代码的最有效方案",挺有道理。

另外想分析的话可以列一下 php 安装的库、扩展,还有对应版本,看看有无懂哥知道某个库出现过内存泄漏的
reyleon
2020-11-19 15:00:42 +08:00
@lovecy 代码是可以改的,问题是得先找到内存上涨的原因才改的动呀。

#21 楼说的"max_requests 我觉得并不是妥协,而是针对大量不可控代码的最有效方案",挺有道理。当前我也是这么做的,但是感觉不爽。反正能用就行,这要求也忒低了点吧?

# php -m
[PHP Modules]
bcmath
Core
ctype
curl
date
dom
ereg
fileinfo
filter
gd
hash
iconv
json
libxml
mbstring
mcrypt
mongo
mongodb
mysql
mysqli
mysqlnd
openssl
pcntl
pcre
PDO
pdo_mysql
pdo_sqlite
Phar
posix
redis
Reflection
session
SimpleXML
sockets
SPL
sqlite3
standard
tokenizer
xml
xmlreader
xmlwriter
zip
zlib

版本就不知道了。
keepeye
2020-11-19 15:05:26 +08:00
可能是代码、第三方模块导致内存泄漏,你也无法控制代码质量,只能通过一些措施减少影响吧
比如:
php.ini 设置 max_memory 小一些
pm.max_requests 设小一些
夜深人静的时候重启一下 php-fpm
wangritian
2020-11-19 16:04:42 +08:00
@reyleon 确实是野路子,但完美解决方案成本不可控啊,而且这个野路子也没什么副作用。举个极端例子,假如 bug 扩展的所有版本都有泄露,而你又不得不依赖它,怎么办?哪怕现在解决了 bug,为了防止后面更新再次泄露,也推荐设置 max_requests
liuyibao
2020-11-19 16:29:02 +08:00
假如是偏业务的接口,大部分是业务代码写的不好,比如数据慢查询等等。导致进程等待,瞬时进程数过大。所以检查下你的接口响应时间是多少,太慢的话肯定是业务代码的问题。
yc8332
2020-11-19 17:11:23 +08:00
请求量大了占用内存不是很正常的吗?配置好相应的最大进程数就好了。。如果是随着时间推移内存占用多,那就是你的 php 代码内存泄露,正常是不会。
Evilk
2020-11-19 17:27:18 +08:00
@reyleon 怎么我之前看到的你的回答,不一样,奇怪
我针对的是你之前说的"请求结束,进程关闭"
老的 cgi 模式,请求结束后,进程会关闭
php-fpm 模式下,请求结束,当前进程并不会退出
仅此而已

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/727028

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX