1
hbolive 2020-11-19 11:14:22 +08:00
没研究过,只能凭感觉说下。
其实说法二和三并不冲突,PHP 确实会自动释放内存。说法二的意思估计是:PHP 并不会立刻释放不需要的资源,而如果频繁的申请内存,导致原先的无用的资源还没来得及释放,所以 PHP 只能去申请新的内存。。 水平有限,以上纯属猜测。。 |
2
wei745359223 2020-11-19 11:17:19 +08:00 1
这种情况也有可能是代码上出了问题。
|
3
lbp0200 2020-11-19 11:17:26 +08:00
PHP 就是这样的异步处理模型,一个请求一个进程,1000 个并发就是 1000 个 PHP 进程,请求结束,进程关闭。
所以,NGINX 后面使用多个 PHP 服务器,就是加机器,这样业务峰值的时候,就不会崩溃了。 1 台 PHP 机器不够,就 2 台,没有一万台服务器解决不了的问题。 |
4
dawniii 2020-11-19 11:25:30 +08:00
第三方扩展是可能存在内存泄露的,之前遇到过 curl 某个版本有问题,升级就好了。
|
6
young 2020-11-19 11:31:16 +08:00
大概率代码问题, 之前用 xhprof 分析过代码
https://www.php.net/manual/en/book.xhprof.php |
7
dawniii 2020-11-19 11:47:12 +08:00
@dawniii 之前 curl 是 cpu 涨,还不是内存。内存出问题,基本没遇到。可以看看数据统计是统计的真实占用的物理内存,还是带 buffer 的?
|
8
sgq1128 2020-11-19 11:49:48 +08:00
每天凌晨定时重启下呗
|
9
buaacss 2020-11-19 11:58:39 +08:00
php 很多 c 的扩展都有内存泄漏的 bug,可以用 valgrind 试试,如果内核支持的话 epbf 也有相应的工具来看内存泄漏
|
10
nuk 2020-11-19 11:59:21 +08:00
一般情况,每个 php 的 process 会加载很多库,可以试试 preload 一些内存占用比较多的库
这样就可以共享内存使用了,可以少很多内存,不过仅限 7.4 以上 我之前在 php5 上做过类似的东西,我们自己的服务器大概一个 php 进程能少 100M 左右。 |
12
ben1024 2020-11-19 12:13:27 +08:00
排查下代码中是否有长连接没有释放,例如 mongodb 一类
|
13
sagaxu 2020-11-19 12:26:56 +08:00 via Android 1
php 扩展良莠不齐,内存泄露和 coredump 是家常便饭,pm.max_requests 调到 100 保平安
|
14
nuk 2020-11-19 12:31:20 +08:00
@ben1024 如果啥事都不干的话大概 100M 左右,处理请求的话大概多加 10~20M 左右吧,如果 php5 把该加载的提前加载好,一个 100ms 的请求可以优化到 10ms 左右。
不过现在我们换 php7 了。。。 |
19
liuxu 2020-11-19 13:49:57 +08:00 4
都是对的
首先内存确实是 zend 一次申请一块大内存,而不是系统调用,因为系统调用代价很高 php 脚本代码释放掉的内存也是给了 zend 内核,而不是还给系统 引用计数释放掉的内存和 php_request_shutdown 释放掉的内存都是还给 zend,zend 不还给系统 fpm 的运行原理是: a. fpm 是多进程的,是同步 io,也就是 php 脚本代码调用 io 请求会阻塞,例如 http 请求,mysql 请求,文件读写 b. fpm 的每个进程有自己的 zend 内核在运行,每个进程维护自己的内存块 c. fpm 可以设置最大进程数,避免内存使用过高,例如 20 个,不用设置太大,因为 cpu 上下文切换在高并发时返而会消耗大量 cpu,具体根据业务请求阻塞 io 调整 d. fpm 可以设置每个进程可以接受的请求数,超过这个请求数就结束进程重新再起一个,避免内存泄露 根据以上可知,想调整 fpm,需要关注内存和接收并发的能力,文档: https://www.php.net/manual/zh/install.fpm.configuration.php 下面给出例子: 1. 楼主为了避免内存一直占用,需要限制 php 进程数。根据楼主的硬件配置,目前不知道楼主的业务,这里给出假设。 2. 设置 pm 为 dynamic,然后设置 max_children 为 64,这样可以限制 fpm 最大启用 64 个进程。 3. 设置 start_servers 为 16,为 fpm 启动时为 16 个进程,这样 fpm 可同时处理 16 个请求。 4. 设置 min_spare_servers 为 16,这样空闲时最小为 16 个进程,max_spare_servers 为 32,空闲时最大为 32 个进程。至于 fpm 空闲时到底会因为什么原因在这个区间伸缩,等我有时间看了相关内核源码再说。。 5. 设置 max_requests 为 10240,为每个进程处理 10240 个请求进程就结束进程重启起一个新的,避免内存泄露。 注:可以根据 xhprof ( php7 可以使用 Tideways )做分析 |
20
liuxu 2020-11-19 13:56:22 +08:00
@liuxu 如果楼主设置的进程数后,发现并发变低,cpu 负载消耗也低,可以适当调大数值。大概就是 1s 内,100ms 是 cpu 运行,900ms 是 io 等待的话,可以把 1 个进程调整到 5-10 个进程。至于为什么不是直接 10 个进程,是因为考虑进程数多了 cpu 上下文切换带来的消耗,有时候 10 个进程还没有 8 个进程并发高。
|
21
wangritian 2020-11-19 14:01:40 +08:00
max_requests 我觉得并不是妥协,而是针对大量不可控代码的最有效方案
如果你想深究,建议分析业务代码,通过压力测试定位到泄露内存的函数,如果是第三方类引起的,仔细读一下他的文档,是否忘记释放对象或某些关键方法 |
22
Evilk 2020-11-19 14:04:14 +08:00
@lbp0200 你的" Reply 3
lbp0200 2 小时 43 分钟前 PHP 就是这样的异步处理模型,一个请求一个进程,1000 个并发就是 1000 个 PHP 进程,请求结束,进程关闭" 看得出来,你对 PHP 的认识,还是比较老旧,建议更新下 |
23
fenglangjuxu 2020-11-19 14:04:46 +08:00
我觉得升级 php 到 7 可能比较简单点.当然可能还得修改一些代码不兼容的地方.
debug 工具 可以试下这个 phptrace 360 出的 |
24
sunznx 2020-11-19 14:18:05 +08:00
招个 php 的维护不行吗
|
25
pigfly123 2020-11-19 14:20:22 +08:00
1. 检查 fpm 配置是否合理,看看处理每个请求占用的内存来设置一个合理的值;
2. 利用 xhprof 去线上开启内存分析采样,基本能定位到内存占用高的具体位置,然后优化代码。 |
27
reyleon OP @liuxu 还是不能理解 php-fpm 进程内存使用会慢慢上涨的原因。
最初启动时一个 php-fpm 进程占用大概 20M 内存,随着接受请求数的增加,内存慢慢会往上涨到 120M 左右,这个时候达到了我们设置 pm.max_requests 的值,然后进程销毁,如何周而复始。 如你所说 “引用计数释放掉的内存和 php_request_shutdown 释放掉的内存都是还给 zend,zend 不还给系统”。 那我这个内存一直往上涨,说明是 Zend 一直在向 OS 申请内存,这没错吧。 那这是不是可以说 “引用计数” 和 “php_request_shutdown” 根本就没有释放过内存?因为如果释放了内存给 Zend,那就说明 Zend 手里有空闲内存,那就不用向操作系统申请啦。 另:我现在正在研究如何使用 xhprof 分析业务代码。 我也是操碎了心。 |
28
reyleon OP @wangritian 可能如你所说,因为同样的 php 环境,只是不同业务的另外一台服务器就没有出现过内存上涨的情况,内存使用稳如狗。
|
30
reyleon OP |
31
C603H6r18Q1mSP9N 2020-11-19 14:35:54 +08:00
php-fpm 我们接触下来 无解!!!
并发高了,会吃内存和数据库链接不释放,导致整套系统垮掉,解决是高并发 上 java |
32
reyleon OP @Evilk 一个请求对应一个 PHP 进程,请求处理结束,这个进程才能处理下一个请求。1000 个并发如果没有那么多 php 进程处理,那么那些请求就在队列里排队等待处理。
我也是这么理解的。这有问题吗?需要更新对 PHP 哪方面的认识? |
34
lovecy 2020-11-19 14:49:12 +08:00
@reyleon 一个 php-fpm 进程就是一个 cgi 进程咯,也就是对应一个 zend 。那些第三方库、扩展啥的,都是自己的内存池吧。如果调用一次 php 代码,运行一次扩展,扩展申请一次 zend 的内存而不释放,代码调用次数增加,慢慢 zend 内存就不够用了。这时候销毁 cgi 进程重启,就能释放掉这些内存。
如果没法改代码,就按 19 楼的说法调优 fpm 咯,反正能用就行,#21 楼说的"max_requests 我觉得并不是妥协,而是针对大量不可控代码的最有效方案",挺有道理。 另外想分析的话可以列一下 php 安装的库、扩展,还有对应版本,看看有无懂哥知道某个库出现过内存泄漏的 |
35
reyleon OP @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 版本就不知道了。 |
36
keepeye 2020-11-19 15:05:26 +08:00 1
可能是代码、第三方模块导致内存泄漏,你也无法控制代码质量,只能通过一些措施减少影响吧
比如: php.ini 设置 max_memory 小一些 pm.max_requests 设小一些 夜深人静的时候重启一下 php-fpm |
37
wangritian 2020-11-19 16:04:42 +08:00
@reyleon 确实是野路子,但完美解决方案成本不可控啊,而且这个野路子也没什么副作用。举个极端例子,假如 bug 扩展的所有版本都有泄露,而你又不得不依赖它,怎么办?哪怕现在解决了 bug,为了防止后面更新再次泄露,也推荐设置 max_requests
|
38
liuyibao 2020-11-19 16:29:02 +08:00
假如是偏业务的接口,大部分是业务代码写的不好,比如数据慢查询等等。导致进程等待,瞬时进程数过大。所以检查下你的接口响应时间是多少,太慢的话肯定是业务代码的问题。
|
39
yc8332 2020-11-19 17:11:23 +08:00
请求量大了占用内存不是很正常的吗?配置好相应的最大进程数就好了。。如果是随着时间推移内存占用多,那就是你的 php 代码内存泄露,正常是不会。
|
40
Evilk 2020-11-19 17:27:18 +08:00
@reyleon 怎么我之前看到的你的回答,不一样,奇怪
我针对的是你之前说的"请求结束,进程关闭" 老的 cgi 模式,请求结束后,进程会关闭 php-fpm 模式下,请求结束,当前进程并不会退出 仅此而已 |
41
joyqi 2020-11-19 17:40:05 +08:00
php 的性能没有很多人想象得那么烂,这个帖子里很多人的服务器资源称得上奢侈了。楼主提供的信息有限,你的服务器并发量现在是多少,平均响应时间是多少,是否存在慢查询,服务器的内存占用监控图是否可以提供下。
|
42
JasperYanky 2020-11-19 17:45:13 +08:00
加机器!
业务跑的好:这么赚钱的业务,加点机器怎么了? 业务跑的不好:业务这么差还让人投入精力改代码改配置?加机器就完事了! |
43
ladypxy 2020-11-19 17:53:19 +08:00 via iPhone
不需要的模块不要加载
换 php 7.3 php 效率其实很高,一般都是你设置问题 |
45
reyleon OP @joyqi 并发量其实很低,目前日 PV 90 万不到,我之前自己做过压测,我们这个接口服务器性能其实很差,估计并发撑不过 40,但性能问题目前并不是我急需解决的。
主要是 php-fpm 进程吃内存,会慢慢往上涨,这才是我想快点解决的。 如果不设置 pm.max_requests, 它可以吃完机器所有的内存。 另:V2EX 貌似无法上图? |
46
zhenhuaYang 2020-11-19 19:18:44 +08:00
@liuxu 666666 啊
|
47
MeteorCat 2020-11-19 19:23:24 +08:00 via Android
ulimit 多少
|
48
seth19960929 2020-11-19 19:34:06 +08:00 via Android
如果 php-fpm 有内存泄漏,不太可能有这么低级的错误
我从优先级给你排查 你的代码有调用系统命令吗?或者守护进城服务,比如 kafka 是否有定时任务执行常驻内存的 PHP 脚本任务 你在内存占用高的时候 top 一下看有几个 work 进程 |
49
liuxu 2020-11-19 19:47:47 +08:00
@zhenhuaYang 又被你发现了
|
50
CODEWEA 2020-11-19 20:18:38 +08:00
又来黑 php,你的 pv 才 90 万,就算是设置成 static=30 都没问题,要想找到解决办法,还得分析具体业务
|
51
ben1024 2020-11-19 20:49:20 +08:00
@shanghai1998
不太赞同换语言就能彻底解决并发问题,其他语言优势是有,但是开发者本身水平才是关键 |
52
everyx 2020-11-19 21:47:48 +08:00
@ben1024 之前用 https://github.com/DarkGhostHunter/Preloader 这个给项目上了 preloading,你可以试试
|
53
anerevol 2020-11-19 22:40:44 +08:00
可能是下面说的这个问题么
你这个基本属于能必现的问题 理论上用二分法很快能定位问题的 https://bugs.php.net/bug.php?id=76436 [2019-08-14 21:50 UTC] phpbug at ethaniel dot com I have this problem in PHP 5.6.40 on Centos 7.6. This simple code triggers it. I just read around 1 million rows from the table and my memory usage is just growing higher and higher. $cnt = 0; $result = mysqli_query($conn,"SELECT `text`,`sms_date`,`to` FROM `sms_data`.`sms_201933`;"); while ($row = mysqli_fetch_object($result)) { if ($cnt%1000) { echo memory_get_usage()." *** \n\n"; } $cnt++; } |
54
huangsen365 2020-11-19 23:57:43 +08:00
用 remi php 7.x
用 docker 构建镜像 上多节点上负载均衡 数据库使用读写分离 |
55
IDAEngine 2020-11-20 01:35:54 +08:00 via iPhone
没遇到过,内存上涨不释放很大原因是代码写的有问题
|
56
reyleon OP @anerevol 感谢!不过我看了下,应该不是这个问题,我搜索了一下,代码中并没有涉及 mysqli 的代码,事实上都没有连接 MySQL 服务。有连接的只有 redis
|
58
rqrq 2020-11-20 10:24:23 +08:00
就是代码问题,自己一点点的去掉代码查吧。
之前用 swoole 弄了个 http server,按照文档 “捕获 Server 运行期致命错误” 加了 register_shutdown_function 放到 onRequest 里面,结果就是内存泄漏。 |
59
onion83 2020-11-20 10:26:09 +08:00
安利一下 Swoole tracker https://business.swoole.com/tracker/index
|
60
litujin1123 2020-11-20 13:41:09 +08:00
@reyleon 那不是也应该排查一下 redis 部分?
|
61
Varobjs 2020-11-20 17:53:47 +08:00
可能 99%是业务代码写的问题
重构吧 |
62
liuxu 2021-02-09 01:37:04 +08:00
@liuxu #19 刚刚看了 min_spare_servers 和 max_spare_servers 相关源码,php-fpm 有一个定时器每秒检测一下
如果进程状态为 FPM_REQUEST_ACCEPTING,也就是没有处理请求,处于闲置状态,而且这些闲置进程的数量 idle 超过了 max_spare_servers,就会每秒 kill 掉一个闲置进程 如果闲置进程的数量 idle 小于 min_spare_servers,就会美妙创建一个进程,直到 min_spare_servers 个,但这有个前提是目前正在运行的进程数量小于 pm_max_children,否则不会创建 |