记录一次神坑操作–导出500万的数据

  •   
  • 4040
  • PHP
  • 0
  • super_dodo
  • 2018/06/15

有时候不得不承认自己笨得可以.

接到任务需求是:有一家重点客户公司想把他们自己的数据导出来,大概有500万条数据。主要的数据存储在mongoDB里面。为了数据的直观性,数据还会关联到MySQL以及PostgreSQL去查询出一些用户或客户的信息。

拿到需求的时候觉得好像没什么难度,虽然对这部分数据和功能不太了解,毕竟界面上有接口,参照接口层就可以理清逻辑。后来又各种杂事和开会,到快下班了,发现自己的任务还没开始写,略显恐慌。

开始自己的搬砖过程。先在测试环境把需要的逻辑和数据组织一下,因为不清楚原先的逻辑,还是有些费神。问人是个好办法,但是问之前自己得先理清楚逻辑,而且有时候自己觉得应该自立一些,独立完成任务。就这样拼拼凑凑少量的数据能够导出了。

接来下放到线上的测试服务器去跑脚本了。各种错误一箩筐,登录态,异常数据,超时等等.先一个一个的解决。继续跑,几千条没什么问题,多了之后又内存不足。

开始我怀疑是我写文件的大小限制了,首先拆分文件。再去跑,发现不是,当数据跑到十几万的时候,是mongodb内存不够了。我把每页查询的数值从1000条,不断的调整成了500,200,100,当数据跑到十几万的时候,进程还是因为内存不足而挂掉了。比较疑惑,后来问了一下有过大数据处理经验的大神。mongodb里面数据量大的时候不能用limit().skip()的形式进行分页查询。要用上一次的最大id进行下一次的查询,而且不能用排序,mongodb大了之后索引会很大,排序会导致内存不足。修改之后再次上线去做实验。


public function actionRunTask()
{
header('content-type:text/html; charset=utf-8');
set_time_limit(0); //不限时
ini_set('memory_limit','2048M'); //内存

$client_id = 1;
$user_id = 1;
$pageSize = 1000;
$maxId = 0;
for ($i = 0; $i <= 5000; $i++) {
$maxId = $this->trailList($client_id, $user_id, $maxId, $pageSize);
file_put_contents('/tmp/dodo_trial_cnt.log', $i.PHP_EOL, FILE_APPEND);
usleep(200);
}
}

public function trailList($client_id, $user_id, $maxId, $pageSize)
{
$params['client_id'] = MongoObject::int32Value($client_id);
$params['_id'] = ['$gt' => $maxId];
$limit['limit'] = MongoObject::int32Value($pageSize);

User::setLoginUserById($user_id);

$cursor = CompanyTrail::getCollection()->find($params, $limit);
$dataList = [];
foreach ($cursor as $item) {
$itemData = MongoObject::trimBsonDocument($item);
$dataList[] = $itemData;
}

$resultPath = '/tmp/dodo_trial_all.csv';
$fp = fopen($resultPath, 'a+');
fwrite($fp,chr(0xEF).chr(0xBB).chr(0xBF)); //防止乱码

$result = [];
foreach ($dataList as $item) {
$row['trial_id'] = $item['_id']; //动态ID
//.........此处省略其他字段和逻辑
$row['create_time'] = $item['create_time']; //创建时间
fputcsv($fp, array_values($row));
}
fclose($fp);
$maxKey = count($dataList) - 1;
$maxId = intval($dataList[$maxKey]['_id']);
return $maxId;
}

其他相关注意的点


/data/app/protected/yiic dodo runTask &
//& 后台运行 但是终端断开,进程就结束了

后台去跑脚本
//nohup /data/app/protected/yiic dodo runTask >> /tmp/dodo_error.log

不要担心文件太大会影响内存
//file_put_csv
//fwrite($fp,chr(0xEF).chr(0xBB).chr(0xBF)); //解决乱码

导出来的文件需要按照行来分割的语句
//split -l 10000 demo.csv -d -a 4 do_

//mongodb 数据量大的时候 不能用 limit(100).skip(300000) 不要使用排序

//多任务去跑

因为数据量大且查询逻辑复杂,且脚本写的不好,造成了导出500万数据用了差不多5个多小时,导出的文件1G左右。原本是晚上跑的,前半段正常,就睡了,第二天早上来看任务失败了。所以跑了好几次,问题定位也不够准确。要完善和提高的地方很多。

要反思的地方还很多,马上端午了。先去武功山浪一下,回来好好反思和学习。祝大家端午节快乐。