匯出處理耗時的檔案

GetaChan發表於2019-12-27

業務場景

業務部門每月都需要匯出上個月(按規定格式處理)的銷售資料進行二次統計分析

需求分析

匯出頻率:每月;匯出範圍:上個月;按規定格式處理:比較耗時;

本地伺服器:每處理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秒不到,效果還不錯:)

相關文章