在使用laravel执行长时间命令行脚本操作数据会导致内存溢出,辛苦各位同学指点

laravel版本:6.20.19 , 8.35.1
PHP版本:7.3.16 , 7.4.16
操作系统:macOS 11.2.3 , centos7
脚本类型:artisan console command

问题描述:
我需要一个长时间监控某项数据的脚本,但是在使用过程中出现内存泄露,尝试了以下方法:

  1. 销毁了所有的不再使用的变量
  2. 使用模型静态方法:firstOrCreate 之前使用了unsetEventDispatcher
  3. DB的insert之前使用了unsetEventDispatcher;
  4. 原生PDO操作
  5. 修改了flare.php将所有的true都改成了false
  6. 使用事件机制,在监听器中创建db对象,执行sql操作,销毁db对象,销毁事件对象

发现内存一直都在增加,没有得到释放,但是不使用框架时,使用PDO操作,内存得到释放,控制的很好。
后面根据 @振翅飞翔 的评论提醒下,删除了 composer remove --dev facade/ignitio 也没有变化,不过却让我意识到可能还是日志的问题,于是把代码里面的Log调用注释掉,发现内存稳定了。
但是仍旧不知道应当如何解决此问题,因为在项目中日志是必要的。

目前基本的结论是:

  1. 数据库操作确实会导致内存溢出
  2. Log组件也会导致内存溢出

以下是示例代码:

while(true){
        $signal_record = [
            'column1'                  => mt_rand(1,200),
            'column2'                  => 'testB',
            'type'                        => 'LIMIT',
            'signal'                      => 'IN',
            'opening_time'          => date('Y-m-d H:i:s'),
            'closing_time'            => date('Y-m-d H:i:s'),
            'strategies_name'      => 'B',
        ];
        $this->info(date('Y-m-d H:i:s') . ' 内存占用' . memory_get_usage());
        TradeSignalRecords::unsetEventDispatcher();
        $r = TradeSignalRecords::create($signal_record);
        //$r = ModelsTradeSignalRecords::firstOrNew($signal_record);
        Log::debug( ' 发现信号 ' .json_encode($signal_record));
        $r = null;
        $this->info(date('Y-m-d H:i:s') . ' 内存占用' . memory_get_usage());
        sleep(1);
}

使用DB操作的示例代码

while(true){
        $signal_record = [
            'column1'                  => mt_rand(1,200),
            'column2'                  => 'testB',
            'type'                        => 'LIMIT',
            'signal'                      => 'IN',
            'opening_time'          => date('Y-m-d H:i:s'),
            'closing_time'            => date('Y-m-d H:i:s'),
            'strategies_name'      => 'B',
        ];
        $this->info(date('Y-m-d H:i:s') . ' 内存占用' . memory_get_usage());
        $connection_name = 'test';
        $table_name      = 'trade_records';
        $connection = DB::connection($connection_name)->table($table_name);
        $connection->getConnection()->unsetEventDispatcher(); // https://learnku.com/laravel/t/52660?order_by=vote_count
        $this->_connection->insert($signal_record);
        Log::debug( ' 发现信号 ' .json_encode($signal_record));
        $connection      = null;
        $signal_record   = null;
        $connection_name = null;
        $table_name      = null;
        $this->info(date('Y-m-d H:i:s') . ' 内存占用' . memory_get_usage());
        sleep(1);
}

使用PDO版本代码示例,此代码在laravel command中内存没有回收,但是不用框架时内存得到了释放

while(true){
    echo date('Y-m-d H:i:s') . ' 内存占用' . memory_get_usage() . "\r\n";
    $conn = new \PDO("mysql:host=localhost;dbname=test", 'root', '123456');
    $conn->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
    $sql = "INSERT INTO trade_records (column1, `column2`, type,`signal`,opening_time,closing_time,strategies_name)  VALUES (" . mt_rand(1,100). ", 'testB','LIMIT','IN','2021-04-01 19:15:00','2021-04-01 19:15:00','B')";
    // 在laravel框架中使用下面这句日志记录
    // Log::debug( ' 发现信号 ' .json_encode($signal_record));
    $r    = $conn->exec($sql);
    $conn = null;
    $r    = null;
    $sql  = null;
    sleep(1);
}

参考资料:
有什么好办法解决进程内存一直增长
DB 添加数据如何实时释放掉内存?
PHP 常驻任务不会释放内存的吗?
Laravel 控制台程序 循环执行 ORM 查询 内存溢出
记一次laravel 内存泄漏
【PHP内存泄漏案例】PHP对象递归引用造成内存泄漏
Laravel7使用日志内存会一直往上增长最后导致内存泄漏

《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 13

$this->info(date('Y-m-d H:i:s') . ' 内存占用' . memory_get_usage());

这个是输出到哪里

3年前 评论
sjlaravel (楼主) 3年前

把 connection 操作放到 while 外部去吧

sleep 1s 的话, 直接 crontab 管理而不是 while true 会不会好点, 这样每次执行完都能清理掉内存

3年前 评论
sjlaravel (楼主) 3年前
Complicated 3年前
DongXin 3年前
3年前 评论
sjlaravel (楼主) 3年前
振翅飞翔 (作者) 3年前

file

这是两个不一样的模型对象,所以第二个模型依然会使用事件。

TradeSignalRecords::unsetEventDispatcher(); 改成 DB::connection()->unsetEventDispatcher();

3年前 评论
sjlaravel (楼主) 3年前
Complicated

你这是 一个常驻内存的进程了,所以得用supervisor管理,把whiler(true)写到artisan命令里,然后用supervisor去监控这个srtisan就可以了

file

3年前 评论
sjlaravel (楼主) 3年前

最近刚好也在纠结脚本长时间运行内存溢出的问题,我的场景是 kafka 消费场景,两套一模一样的代码一个消费数据量小就没有任何问题,另一个消费数据量大内存就在不断的缓慢增长,排疑目前也没有什么好的思路。

3年前 评论

你改这种常驻内存型代码不如开个定时任务来做,又没差在哪里

3年前 评论
Complicated

@lidongyoo crontab最多是一分钟执行一次,他这个是要秒级的监控

3年前 评论

@lidongyoo @Complicated 更多时候是因为业务耗时比较长,只能单线程操作。

3年前 评论

@Complicated github.com/spatie/laravel-short-sc...
@Where 为什么耗时长只能单线程?不知道你在说什么

如果非要 while true 的话,可以参考鸟哥的文章:PHP CLI模式下的多进程应用

3年前 评论
Complicated

@lidongyoo

file一样的,也是一个常住内存的进程,然后用supervisor 守护,当做容器,再写一些小的任务,这样实现秒级定时任务

3年前 评论
lidongyoo 3年前
Complicated (作者) 3年前

不想用 crontab 可以装一个毫秒级的定时器包啊。我记得有的啊。while(true)确实不好。

3年前 评论
Complicated

@huanbird 毫秒级的定时包也得有workman 或者swoole之类的辅助工具才能实现毫秒级的吧

3年前 评论
Fell-boy 3年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!