ThinkPHP執行長時間任務時,可能導致PHP使用內存越來越大,最後因為內存超出配置限額而程序掛掉。
其實這在很久以前就無意之中發現的一個問題。
3.x之前有這個問題,5.0以後的,應該是已修復了的(我看了下5.0.6)。
這個問題,一句話説明,是因為ThinkPHP在記錄系統日誌的時候出現的問題(ThinkPHP在運行過程中,會記錄自己的運行日誌,根據是否開啓DEBUG模式,記錄的日誌信息有不同,不多説)。
因為一個進程執行完,會在很多地方打點記錄日誌,但是為了提高寫日誌文件的效率,ThinkPHP先是給所有日誌信息記錄在內存裏(一個數組),最後一次性寫入文件。正是因為這個科學的做法,導致了可能出現的不科學的結果,那就是,在長時間執行循環任務時,尤其是會循環操作數據庫時而又開啓了DEBUG模式的情況下(日誌信息會記錄數據庫語句等,信息較多),就會有大量的日誌信息,越來越多的存入這個數組裏,造成內存佔用越來越大,最終可能導致程序奔潰。
看代碼:
/**
* 文件:ThinkPHP\Library\Think\Log.class.php
* 記錄日誌 並且會過濾未經設置的級別
* @static
* @access public
* @param string $message 日誌信息
* @param string $level 日誌級別
* @param boolean $record 是否強制記錄
* @return void
*/
static function record($message,$level=self::ERR,$record=false) {
if($record || false !== strpos(C('LOG_LEVEL'),$level)) {
self::$log[] = "{$level}: {$message}\r\n";
}
}
如果遇到這種情況,修改一下此方法即可。這裏提供一個最簡單的修改方法(在未開啓DEBUG模式的情況下,直接不記錄日誌):
static function record($message,$level=self::ERR,$record=false) {
if( !APP_DEBUG ) return false; //在debug為開啓情況下,不記錄日誌 by ztg
if($record || false !== strpos(C('LOG_LEVEL'),$level)) {
self::$log[] = "{$level}: {$message}\r\n";
}
}
這樣,當執行長時間的循環任務時,給DEBUG關閉後,則無日誌記錄,可不會再出現這種情況了。
另外,附帶ThinkPHP5.0後的代碼,可以看出,在保存日誌的機制上,已經完全變了,會循環的定量保存,然後清空內容,避免這種情況:
/**
* 記錄調試信息
* @param mixed $msg 調試信息
* @param string $type 信息類型
* @return void
*/
public static function record($msg, $type = 'log')
{
self::$log[$type][] = $msg;
if (IS_CLI && count(self::$log[$type]) > 100) {
// 命令行下面日誌寫入改進
self::save();
}
}
/**
* 保存調試信息
* @return bool
*/
public static function save()
{
if (!empty(self::$log)) {
if (is_null(self::$driver)) {
self::init(Config::get('log'));
}
if (!self::check(self::$config)) {
// 檢測日誌寫入權限
return false;
}
if (empty(self::$config['level'])) {
// 獲取全部日誌
$log = self::$log;
if (!App::$debug && isset($log['debug'])) {
unset($log['debug']);
}
} else {
// 記錄允許級別
$log = [];
foreach (self::$config['level'] as $level) {
if (isset(self::$log[$level])) {
$log[$level] = self::$log[$level];
}
}
}
$result = self::$driver->save($log);
if ($result) {
self::$log = [];
}
return $result;
}
return true;
}