PHP如何实现百万级数据导出

PHP如何实现百万级数据导出

公司目前有一个需求,需要对一个日增量在20万+数据量的数据表中的数据进行可自定义条件筛选的导出数据,该功能需要对多个部门进行开发使用,要保证功能可用的前提下,尽量优化体验。

首先介绍一下当前可利用的资源:

1、MySql – 一主库双从库。

2、分布式服务器集群,选择其中一台中型机作为脚本执行载体。

3、文件系统 – 可以支持上传大数据量文件。

4、编程语言PHP,说实话PHP不太适合来干这个事情。

 

技术难点:

1、数据太大,对服务器配置要求较高,导出过程中涉及数据的处理(例如各种ID转换名称等操作,我们这次需求这种太多了~~非常的坑)对内存消耗很大,其次涉及到文件压缩,因此对CPU要求较高。

2、因为是跨系统部署,如果走接口,数据量随随便便上百M,传输速度太慢(项目是对外网开放的,然后数据只允许内网访问),那么该如何解决?

3、数据安全性较高,需要对所有导出进行记录,那么如何保证数据安全?

 

| 技术方案

第一步:设计数据库,对所有导出任务进行实时记录,也可以采用redis,为了方便数据的持久化,我最终采用了mysql数据库的方案。表结构具体包括:ID、用户ID、用户名、发起请求时间、导出具体的参数(包括各个维度的参数选择等,具体根据自身业务而定),任务是否正在处理标识(防止任务多次被处理),导出是否成功标识(可以与前一个用一个字段区分),删除标识等(假删除,便于记录用户实际操作日志)。

第二步:前台界面编写,具体包括参数选择、导出记录列表等,作用:触发导出任务创建,记录于导出表中,状态:待处理。

第三步:编写导出脚本对任务进行监控并处理,如果有导出任务自动对其执行导出操作。

这里有一个小问题:为什么不在前台触发任务的时候直接执行导出,而是有单独的脚本来执行导出呢?这就是现实业务导致的,因为我们对外开放的机器中有一些是配置很低的,为了保证导出的成功率,我们需要一台配置较高的机器来独立执行导出任务。

| 导出流程

具体流程参考下图,自己画的凑合看吧~~~哈哈哈

PHP如何实现百万级数据导出

 

代码实现

这里主要着重介绍一下导出脚本的代码,其他步骤的代码根据自己的业务自行编写就可以了。

注意:因为数据量过大~一次性导出可想而知是不合理的,所以我使用了分页导出的形式~

首先查询数据总条数、然后通过每页导出的条数来计算具体导出的页数~

        # 获取数据总条数
        $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); } }

最后将生成好的文件存入文件系统,上传成功之后反转导出状态,前台检测到导出成功自动进行下载即可

https://blog.csdn.net/bk_guo/article/details/81947010

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/112689.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 微信新增“炸屎”功能,被好友玩坏了。。

    点击上方“全栈程序员社区”,星标公众号 重磅干货,第一时间送达 微信又有新玩法了,可以通过表情+表情进行互动了,下面一起来看看,感觉还是蛮有趣的,花里胡哨功能又多了。 需要注意的因…

    2021年6月27日
    143
  • 关于gitlab Web IDE功能使用

    关于gitlab Web IDE功能使用gitlabWebIDE使用1,进入gitlab项目路径,可以看到有个WebIDE按钮2,点击WebIDE按钮,跳到下面的界面 2.1.在这里可以建新的文件夹或者文件。 2.2.选中新建的文件夹newfoldr,点击右边的按钮,选择上传文件, 2.3.点击commit 2.4.下面要选择合并到master分支。(如果你们需要每个人需要创建新的分支去合并到 master分支,也可以选下面的createanewbranch)。再点击commit

    2022年10月17日
    2
  • mariadb安装教程linux,CentOS 7下MariaDB10.0.14安装配置步骤详解

    mariadb安装教程linux,CentOS 7下MariaDB10.0.14安装配置步骤详解系统环境:CentOS7MariaDB版本:MariaDB10.0.14下载地址:http://mirrors.neusoft.edu.cn/mariadb/mariadb-10.0.14/source/mariadb-10.0.14.tar.gz其他包地址:https://downloads.mariadb.org/添加一个mysql用户useraddmysql开始编译安装MariaDBw…

    2022年6月11日
    41
  • 【python】获取当前时间戳

    【python】获取当前时间戳importtime 获取当前时间 times datetime now strftime Y m d H M S printtimes 转为时间数组 timeArray time strptime times Y m d H M S 转为时间戳 timeStamp int time mktime timeArray printtimeSta 结果 个人网站 沉默博客如有错误 请多多指教 如对你有帮助

    2025年7月1日
    3
  • 开心网买房子外挂_开心躲猫猫穿墙版下载

    开心网买房子外挂_开心躲猫猫穿墙版下载     开心网的买房子组件出了很久了,竟然到现在还没有出一个买房外挂。上星期某一晚上基于turbozv.com提供的抢车位的源代码,改写了一个买房子的外挂,此外挂不具有抢人住自己家的功能,那个不赚钱。来钱最快的是每隔一小时换一个地方住,随机得0到6000之前的住房津贴。经过一个星期的尝试,平均每天入帐5万,嘿嘿。发给大家一起来挂吧,祝大家早日住上大别墅。…

    2025年11月4日
    4
  • cad图例大全_Dote图层

    cad图例大全_Dote图层之前总是想当然的认为,将N个纹理打包成一个图集,那么这个图集只会产生一个DrawCall,如果不打就产生N个DrawCall,后来才发现这并不是决定DrawCall的唯一因素,它还和层级关系有关······这里就会提到渲染的顺序问题,在渲染时默认会按照深度去渲染,也就是会先渲染离摄像机远的物体,后渲染离摄像机近的物体。例如你按顺序渲染三个物体A、B、C,A和C使用相同的材质1,B使用材质2,这…

    2025年12月11日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号