PHP 常驻任务不会释放内存的吗?

2019-12-19 15:07:47 +08:00
 raysonlu

写了个处理 redis 阻塞队列的脚本任务常驻在系统里面,随着处理的任务数越多,进程占用内存慢慢增大。脚本大致内容如下:

<?php 

$redis = new Redis();
$redis->setOption(\Redis::OPT_READ_TIMEOUT, -1)

//连接 redis 省略

while(1){
	$data = $redis->brpop('queueName',60);
    
    if(!empty($data)){
    	$obj = new someObj();
        $obj->handleSomething($data);
        unset($obj);
    }else{
    	sleep(10);
    }
}

具体查看进程占用内存,我是参考这些参数: ps auxf命令的 RSS 列,以及 top命令的 RES 列

为什么会这样,按我目前理解,每处理一个任务应该都释放部分内存啊,至少看不到会导致占用内存累加的部分,非常疑惑。请教一下各位 dalao 们。是我用的框架有问题?还是 php 的 redis 扩展问题?

爆了设定的占用内存限制会自动结束进程,设置无限内存也有点实际,这问题有点头疼

10337 次点击
所在节点    PHP
61 条回复
ferock
2019-12-19 23:24:33 +08:00
new 一次不就完了,干嘛循环 new
shiny
2019-12-19 23:26:05 +08:00
可以试试 xhprof 诊断
iyaozhen
2019-12-19 23:35:50 +08:00
正常是不会内存泄露的,跑了好几个月的 cli
楼上说了,你这个代码有问题,new 的放在死循环外面,然后 handleSomething 被你隐藏了很多信息呀
shellic
2019-12-20 08:28:38 +08:00
哈哈,PHP 表示这锅我不背
askfilm
2019-12-20 08:33:20 +08:00
PHP 表示这锅我不背 +1
DavidNineRoc
2019-12-20 08:58:28 +08:00
具体代码贴出来,即使释放对象,更正确的做法也应该是 $var = null; 这样没有栈变量指向对象,对象会自动释放
namek
2019-12-20 09:08:15 +08:00
没有更多的细节 目前没办法帮助到楼主。不过希望楼主解决以后能结下帖子 :)
flashrick
2019-12-20 09:10:05 +08:00
疯狂 new
luoyou1014
2019-12-20 09:13:07 +08:00
首先, 确认下 enable_gc 有没有开, 如果更改 php.ini 里面的配置的话, 可以在代码前面加上一句 gc_enable();


php 大量用于 web 应用, 开启 gc 对性能有轻微的影响, 很多版本默认不开 gc, 需要自己开下.
z1154505909
2019-12-20 09:18:54 +08:00
这锅 PHP 不背+2
jimmzhou
2019-12-20 09:23:04 +08:00
楼上那些说在 while 外 new 的 有没有想过每次 new 的 是一个 job 是从 redis pop 出来的一个个 job 你怎么在 while 外面 new
fancy111
2019-12-20 09:32:28 +08:00
你写的代码问题,$redis->brpop 自动阻塞 60 秒,后面又 sleep10。另外$redis->brpop 无返回值你确定 empty 会判断为空吗? brpop 超时后也是会返回一个列表的,这样你就是一直循环生成对象了。
shadowsll
2019-12-20 09:41:38 +08:00
这锅 PHP 不背的,注意一下使用规范,既然是常驻进程,考虑变量使用手动释放就好。听楼主使用的是框架,我说一个我遇到的也是内存持续增长的问题吧,看看对你是否有帮助。
脚本中我使用了框架里面的日志记录,最终查到原因是这个日志记录首先记录在内存中,等着脚本结束后,在统一写入文件的。解决方案是在添加日志前面加一个判断,判断变量大小,大于多少写入文件,清空变量。
fuxkcsdn
2019-12-20 09:46:56 +08:00
PHP 表示这锅我不背 +10086

运行一段时间( idel 多少秒 或 执行了 N 次)建议退出让守护进程拉起

至于守护进程,除了上面推荐的 supervisor,也可以尝试下我写的 https://github.com/consatan/pdaemon

最开始我也是用 supervisor,但长时间没任务的时候,因为 idel 多次重启,结果 supervisor 认为是程序问题就不给我拉起了...(16 年左右,当时查了文档无解,所以才开始写自己的 守护进程)


p.s. brpop 没获取到任务的时候,没必要 sleep(10),应该执行 idel 累加并马上再 brpop,不然在这 10 秒内有任务过来就会卡住了

还有楼上一些纠结不要在循环里 new 的,po 主贴的是示例代码而已,这边是 new someObj(),不是 new 特定类,我估计 someObj 是从队列里获取到的,这个不在 循环里 new 还真没办法

p.s. 再 p.s. 我自己写的守护进程最长运行时间近 2 年(之后上家公司就关了 /手动狗头),在新项目里也连续稳定执行 1 年多了
liuxu
2019-12-20 09:50:53 +08:00
php 版本是多少? redis 模块版本呢?
zjsxwc
2019-12-20 09:58:31 +08:00
php 的 gc 比较弱好像是基于引用计数的,并不能回收对象间存在循环引用导致的内存泄露(没真正确定过求反驳)
简单粗暴点处理就定时重启吧
zjsxwc
2019-12-20 09:59:42 +08:00
@zjsxwc

php5.3 之后 gc 变聪明了,我收回上面的话

https://www.php.net/manual/en/features.gc.collecting-cycles.php
golden0125
2019-12-20 10:00:34 +08:00
在循环中 new 的对象内存能释放吗?
sagaxu
2019-12-20 10:01:35 +08:00
php 部分扩展存在内存泄露问题
back0893
2019-12-20 10:01:58 +08:00
没有遇到过表示,,
平时写的 cli 命令一般是 cron 执行..

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

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

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

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

© 2021 V2EX