1 hbolive 2020-11-19 11:14:22 +08:00 没研究过,只能凭感觉说下。 其实说法二和三并不冲突,PHP 确实会自动释放内存。说法二的意思估计是:PHP 并不会立刻释放不需要的资源,而如果频繁的申请内存,导致原先的无用的资源还没来得及释放,所以 PHP 只能去申请新的内存。。 水平有限,以上纯属猜测。。 |
![]() | 2 wei745359223 2020-11-19 11:17:19 +08:00 ![]() 这种情况也有可能是代码上出了问题。 |
![]() | 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 某个版本有问题,升级就好了。 |
![]() | 5 justseemore 2020-11-19 11:25:52 +08:00 ![]() @lbp0200 你这别误导人。。。 |
![]() | 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 ![]() 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 ![]() 都是对的 首先内存确实是 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,为每个进程处理 0240 个请求进程就结束进程重启起一个新的,避免内存泄露。 注:可以根据 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 keepfun 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 ![]() 可能是代码、第三方模块导致内存泄漏,你也无法控制代码质量,只能通过一些措施减少影响吧 比如: 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 @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 Dragonish3600 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 SethShi 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,否则不会创建 |