首先先容一下当前可利用的资源:
1、MySql - 一主库双从库。
2、分布式做事器集群,选择个中一台中型机作为脚本实行载体。
3、文件系统 - 可以支持上传大数据量文件。
4、编程措辞PHP。
技能难点:
1、数据太大,对做事器配置哀求较高,导出过程中涉及数据的处理(例如各种ID转换名称等操作,我们这次需求这种太多了~~非常的坑)对内存花费很大,其次涉及到文件压缩,因此对CPU哀求较高。
2、由于是跨系统支配,如果走接口,数据量随随便便上百M,传输速率太慢(项目是对外网开放的,然后数据只许可内网访问),那么该如何办理?
3、数据安全性较高,须要对所有导出进行记录,那么如何担保数据安全?
| 技能方案
第一步:设计数据库,对所有导出任务进行实时记录,也可以采取redis,为了方便数据的持久化,我终极采取了mysql数据库的方案。表构造详细包括:ID、用户ID、用户名、发起要求韶光、导出详细的参数(包括各个维度的参数选择等,详细根据自身业务而定),任务是否正在处理标识(防止任务多次被处理),导出是否成功标识(可以与前一个用一个字段区分),删除标识等(假删除,便于记录用户实际操作日志)。
第二步:前台界面编写,详细包括参数选择、导出记录列表等,浸染:触发导出任务创建,记录于导出表中,状态:待处理。
第三步:编写导出脚本对任务进行监控并处理,如果有导出任务自动对其实行导出操作。
这里有一个小问题:为什么不在前台触发任务的时候直接实行导出,而是有单独的脚本来实行导出呢?这便是现实业务导致的,由于我们对外开放的机器中有一些是配置很低的,为了担保导出的成功率,我们须要一台配置较高的机器来独立实行导出任务。
| 导出流程
详细流程参考下图
| 代码实现
这里紧张着重先容一下导出脚本的代码,其他步骤的代码根据自己的业务自行编写就可以了。
把稳:由于数据量过大~一次性导出可想而知是不合理的,以是我利用了分页导出的形式~
首先查询数据总条数、然后通过每页导出的条数来打算详细导出的页数~
# 获取数据总条数$dataCount = Data_ExportModel::getExportZipTotalCount($params);$dataCount = $dataCount[0]['count_num'];# csv# 输出Excel文件头,可把user.csv换成你要的文件名$mark = '/tmp/export';$stepLen = 20000;//每次只从数据库取100000条以防变量缓存太大# 每隔$limit行,刷新一下输出buffer,不要太大,也不要太小$limit = 20000;$maxFileCount = 1000000;# buffer计数器$cnt = 0;$head = self::initColumnDataV2(); // 表头部分根据自身业务自行调度$fileNameArr = array();$salesStatisticsData = array();$startLimitId = 0;
首次导出的每页条数我定的10万条,后来创造对内存花费过大,改成了两万条,这样的导出速率会慢一点,建议五万条比较适中一点。
for ($j = 0; $j < ceil($dataCount / $maxFileCount); $j++) { $startSelect = ceil($maxFileCount / $stepLen)$j; $fileCsvName = $mark . '_'.$j$maxFileCount.'_' . ($j+1)$maxFileCount . '.csv'; $fp = fopen($fileCsvName, 'w'); //天生临时文件 $fileNameArr[] = $fileCsvName; # 将数据通过fputcsv写到文件句柄 fputcsv($fp, $head); for ($i = 0; $i < 50; $i++) { // 单个文件支持100万数据条数 $startNum = $j$maxFileCount + $i$limit; if ($startNum > $dataCount) { break; // 跳出循环 } # 查询数据 $dataSource = Data_ExportModel::getExportZipTotalInfo($params, $startNum, $stepLen, $startLimitId); $endMicroTime = microtime(true); printf(\公众\n[%s -> %s] Begin Time : %s, End Time : %s, Total Count : %s, CostTime: %s.\n\"大众, __CLASS__, __FUNCTION__, $params['begin_date'], $params['end_date'], count($dataSource), ($endMicroTime - $startMicroTime)); if (empty($dataSource)) { continue; } $endMicroTime = microtime(true); foreach ($dataSource as $_key => $_data) { $cnt++; if ($limit == $cnt) { # 刷新一下输出buffer,防止由于数据过多造成问题 ob_flush(); flush(); $cnt = 0; } # 数据处理部分,根据自身业务自行定义,把稳中文转码 $salesStatisticsData['name'] = iconv('utf-8', 'GB18030', $salesStatisticsData['c_name']); fputcsv($fp, $salesStatisticsData); } } fclose($fp); # 每天生一个文件关闭}# 进行多文件压缩$zip = new ZipArchive();$number = rand(1000,9999);$filename = $mark.\公众_\公众.$params['begin_date'].\"大众_\"大众.$params['end_date'] .\公众_\"大众.$number. \"大众.zip\"大众;$zip->open($filename, ZipArchive::CREATE); //打开压缩包foreach ($fileNameArr as $file) { $zip->addFile($file, basename($file)); //向压缩包中添加文件}$zip->close(); //关闭压缩包if (!file_exists($filename)) { // 首次实行检讨天生的压缩文件是否存在失落败,进行二次考试测验。。。 $endMicroTime = microtime(true); # 进行二次多文件压缩 $number = rand(1000,9999); $filename = $mark.\"大众_\"大众.$params['begin_date'].\"大众_\"大众.$params['end_date'] .\"大众_\公众.$number. \"大众.zip\"大众; if (file_exists($filename)) { unlink($filename); } $zip->open($filename, ZipArchive::CREATE); //打开压缩包 foreach ($fileNameArr as $file) { $zip->addFile($file, basename($file)); //向压缩包中添加文件 } $zip->close(); //关闭压缩包}if (file_exists($filename)) { $content = file_get_contents($filename); // 办理读取文件偶尔涌现失落败的问题,第一读出为空则考试测验第二次读取 $forNum = 0; while (!$content) { $forNum++; @$content = file_get_contents($filename); if ($forNum > 10) { break; // 防止涌现非常情形导致去世循环,最多重试10次 } }} else { $endMicroTime = microtime(true); # 删除临时文件,防止占用空间 foreach ($fileNameArr as $file) { if (is_file($file)) { unlink($file); } } // 记录缺点日志并且报警 return false;}# 删除临时文件,防止占用空间foreach ($fileNameArr as $file) { if (is_file($file)) { unlink($file); }}
末了将天生好的文件存入文件系统,上传成功之后反转导出状态,前台检测到导出成功自动进行下载即可。
点击理解更多去学习:非常利用的代码优化,怎么才能写好代码