在使用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使用日志内存会一直往上增长最后导致内存泄漏

《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 13
小李世界

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

这个是输出到哪里

5个月前 评论
sjlaravel (楼主) 5个月前

把 connection 操作放到 while 外部去吧

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

5个月前 评论
sjlaravel (楼主) 5个月前
Complicated 5个月前
DongXin 5个月前
5个月前 评论
sjlaravel (楼主) 5个月前
振翅飞翔 (作者) 5个月前

file

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

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

5个月前 评论
sjlaravel (楼主) 5个月前
Complicated

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

file

5个月前 评论
sjlaravel (楼主) 5个月前

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

5个月前 评论

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

5个月前 评论
Complicated

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

5个月前 评论

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

5个月前 评论

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

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

5个月前 评论
Complicated

@lidongyoo

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

5个月前 评论
lidongyoo 5个月前
Complicated (作者) 5个月前

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

5个月前 评论
Complicated

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

5个月前 评论
huanbird 5个月前

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