Yii2 框架跑腳本時內存泄漏問題分析

現象

在跑 edu_ocr_img 表的歸檔時,每跑幾萬個數據,都會報一次內存耗盡php

PHP Fatal error:  Allowed memory size of 134217728 bytesexhausted (tried toallocate 135168 bytes)

跟蹤代碼發現,是在插入時如下代碼形成的:git

EduOCRTaskBackup::getDb()->createCommand()->batchInsert(EduOCRTaskBackup::tableName(), $fields, $data)->execute();

execute 以後會形成使用內存漲上去,而且在以後 unset 全部變量內存也會有一部分不會刪除,直到內存耗盡。github

因而跟蹤到 Yii2中execute的具體代碼塊發如今記錄 log 的時候會將使用很高的內存,分析代碼以後得出形成泄漏的代碼塊以下:yii2

形成泄漏的代碼塊

/**
 * Logs a message with the given type and category.
 * If [[traceLevel]] is greater than 0, additional call stack information about
 * the application code will be logged as well.
 * @param string|array $message the message to be logged. This can be a simple string or a more
 * complex data structure that will be handled by a [[Target|log target]].
 * @param integer $level the level of the message. This must be one of the following:
 * `Logger::LEVEL_ERROR`, `Logger::LEVEL_WARNING`, `Logger::LEVEL_INFO`, `Logger::LEVEL_TRACE`,
 * `Logger::LEVEL_PROFILE_BEGIN`, `Logger::LEVEL_PROFILE_END`.
 * @param string $category the category of the message.
 */
public function log($message, $level, $category = 'application')
{
    $time = microtime(true);
    $traces = [];
    if ($this->traceLevel > 0) {
        $count = 0;
        $ts = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
        array_pop($ts); // remove the last trace since it would be the entry script, not very useful
        foreach ($ts as $trace) {
            if (isset($trace['file'], $trace['line']) && strpos($trace['file'], YII2_PATH) !== 0) {
                unset($trace['object'], $trace['args']);
                $traces[] = $trace;
                if (++$count >= $this->traceLevel) {
                    break;
                }
            }
        }
    }
    
    // 這裏是形成內存的罪魁禍首
    $this->messages[] = [$message, $level, $category, $time, $traces];
    if ($this->flushInterval > 0 && count($this->messages) >= $this->flushInterval) {
        $this->flush();
    }
}

形成內存泄漏的緣由分析

在 Yii2框架中的 vendor/yiisoft/yii2/log/Logger.php:156 log函數的156行以後會判斷 count($this->messages) >= $this->flushInterval
即:內存中存儲的 message 的條數要大於等於預設的 $this->flushInterval 纔會將內存中的message 刷到磁盤上去。app

若是在刷新到磁盤以前就已經將 php.ini 設置的 128M 內存打滿的話,會直接報錯申請內存耗盡。框架

不少關於 YII2其餘緣由的內存泄漏的討論
https://github.com/yiisoft/yii2/issues/13256yii

解決方案

  1. 在程序開始時,設置 flushInterval 爲一個比較小的值
\Yii::getLogger()->flushInterval = 100; // 設置成一個較小的值
  1. 在程序執行過程當中,每次 execute 以後對內存中的 message 進行 flush
\Yii::getLogger()->flush(true); // 參數傳 true 表示每次都會將 message 清理到磁盤中
相關文章
相關標籤/搜索