業務場景
業務部門每月都需要匯出上個月(按規定格式處理)的銷售資料進行二次統計分析
需求分析
匯出頻率:每月;匯出範圍:上個月;按規定格式處理:比較耗時;
本地伺服器:每處理100條資料耗時20秒,匯出是實時統計,這部分不做更優化處理
線上伺服器:未知
初步設計
耗時任務即時匯出都是不現實的做法;定時任務匯出excel,具體檔案可存放本地或雲端,介面上提供連結下載。
匯出Excel程式碼實現
//https://github.com/Maatwebsite/Laravel-Excel
use Excel;
.
.
.
//匯出excel邏輯
$file_name = '';
$sheet_name = '';
$excel = Excel::create($file_name);
$excel->sheet($sheet_name);
$current_sheet_obj = $excel->setActiveSheetIndex(0)->getSheet();
$first_column = [];//存放列名
$current_sheet_obj->row(1, $first_column);
$rows = [];//資料來源
$current_sheet_row = 2;
foreach ($rows as $row) {
//耗時處理操作得到$row_arr
...
//插入資料
$current_sheet_obj->row($current_sheet_row, $row_arr);
$current_sheet_row++;
}
匯出Excel存在問題
已知線上伺服器,當資料量到達一定程度時,無法順利匯出excel
優化設計
前提:沒有規定必須要excel做複雜的樣式;
放棄直接匯出excel,先匯出txt文字檔案,再手動複製到excel;Linux下只要保證有製表符 "\t" 和換行符 "\n" 即可。
匯出txt程式碼實現
use Storage;
.
.
.
//不限制執行時間
set_time_limit(0);
//最大記憶體
ini_set('memory_limit', '-1');
//匯出txt邏輯
$file_path = $file_name.".txt";
$str = "";//拼接表頭字元
Storage::disk('local')->put($file_path, $str);
//以二進位制的方式追加讀寫
$file = fopen(storage_path().$file_path, "a+b");
$str = "";
$rows = [];//資料來源
foreach ($rows as $row) {
//耗時處理操作得到$str
...
//每處理1條資料就寫入一次,資料量大時,建議批量寫入
fwrite($file, $str."\n");
}
fclose($file);
匯出txt存在問題
使用Storage::disk('local')->append($file_path, $str),資料量到達一定程度時,仍提示記憶體不足
local.ERROR: exception 'Symfony\Component\Debug\Exception\FatalErrorException' with message 'Out of memory (allocated 1197211648) (tried to allocate 96374400 bytes)' in /var/www/my_project/vendor/league/flysystem/src/Util/MimeType.php:28 Stack trace: #0 {main} [] []
解決:
使用fopen($file_path, "a+b"); //二進位制模式追加到檔案末尾;
分批處理,每處理100條追加一次文字
最終優化效果
本地伺服器:最終順利匯出5W條處理耗時的資料,花費3小時左右。
通過top指令觀察,可以看到本地測試伺服器(記憶體1G左右)比較穩定,沒有出現記憶體不足的情況
線上伺服器:匯出5W條處理耗時資料,僅20秒不到,效果還不錯:)