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 扩展问题?

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

10675 次点击
所在节点    PHP
61 条回复
junan0708
2019-12-19 15:48:38 +08:00
运行一段时间自动退出
eason1874
2019-12-19 15:53:38 +08:00
PHP-FPM 就是爱占着内存不释放,你可以去修改配置来限制占用。
php01
2019-12-19 15:57:06 +08:00
路子走歪了,在歪路上玩出花来也是错的
ch3nz
2019-12-19 16:16:13 +08:00
在 unset 之后 加个 gc_collect_cycles(); 强制内存回收试试看。
如果没效果,我怀疑是 redis 扩展的问题。
ksoeasyxiaosi
2019-12-19 16:46:46 +08:00
@eason1874 #2 写的是脚本 跟 fpm 没关系、
mclxly
2019-12-19 18:14:24 +08:00
我之前用 php 写过爬虫,结果脚本经常把内存吃光了,感觉 php 内存管理有问题,然后换别的语言了。
zpfhbyx
2019-12-19 18:23:22 +08:00
之前脚本也是内存爆了, 强制 gc 大数组 先赋值 null,在 unset 也会有增长, 后来改成处理完某批次任务,判断运行时间超过定长 就主动退出,然后靠守护进程拉起..
xjmroot
2019-12-19 18:23:34 +08:00
简单点: 用`memory_limit`参数限制最大内存,monitor 挂了自动拉起
复杂点: 用`memory_get_usage`调试,看内存是啥时候起来的,排查原因
areless
2019-12-19 18:26:06 +08:00
php 不会漏,是你句柄没有释放而已。常驻要用常驻的思路,都得 unset 赋空,redis->close()关闭句柄
eason1874
2019-12-19 18:42:48 +08:00
@ksoeasyxiaosi #5 楼主说看进程占用内存啊,应该是 php-fpm 进程吧?我猜是说它,因为这玩意儿占用内存回收了也不会立即释放的,长时间占着,哪怕用不着。
haiyang416
2019-12-19 18:45:26 +08:00
@eason1874 说的是 CLI 吧。
littleylv
2019-12-19 18:48:28 +08:00
@eason1874 #10 只是命令行 类似 nohup php a.php 这样的,不是 php-fpm。。。
eason1874
2019-12-19 18:49:32 +08:00
@haiyang416 #11 这样,那是我想偏了。
eason1874
2019-12-19 18:50:27 +08:00
@littleylv #12 对,是我想偏了,我没怎么用命令行,刚才没想到这方面。
haiyang416
2019-12-19 18:52:40 +08:00
PHP 进程有自己的内存管理,变量占用的内存在申请后不会直接释放返回系统,而是还给自己的内存池。并且 PHP 是基于计数器进行垃圾回收,你 unset 的数组或者对象如果存在循环引用,会一直保存在垃圾回收池中,默认大概是 10000 个 slot,只有所有 slots 都满了才会进行垃圾回收。
raysonlu
2019-12-19 22:06:32 +08:00
@haiyang416 有无办法强制召唤回收器? 如楼上说的“gc_collect_cycles();” ?
cabing
2019-12-19 22:14:57 +08:00
<?php

set_ini("mem_limitxxxxx", 1024M);


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

//连接 redis 省略

$obj = new someObj();

while(1){

//重新连接下
if(!$redis->ping()){
重新连接下
}
$data = $redis->brpop('queueName',60);

if(!empty($data)){
$obj->handleSomething($data);
}else{
$redis->close();
sleep(10);
}
}


检查下正确性,使用 supervisor 监控下进程。
haiyang416
2019-12-19 22:22:43 +08:00
@raysonlu 这个函数就是手动进行垃圾回收的,不推荐每个队列任务都调用一次,你可以给个概率调用,不过前面也说了,回收了不一定还给系统。更通常的做法是设定条件重启进程,比如处理 1000 个队列任务或者请求后就自动退出,然后由守护进程重新拉起。
GGGG430
2019-12-19 22:45:09 +08:00
问题出在 new someObj ()这里,以及调用这个对象中方法中存在申请内存并且未释放,外部的 unset 是不管用的,建议你复用这个对象
JaguarJack
2019-12-19 23:22:10 +08:00
new 应该在 while 外面

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

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

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

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

© 2021 V2EX