来由
项目需求需要做一个批量数据导出程序,数据量在百万级,解决方案为每次10000条分页取出数据,导出到缓冲区实现持续导出csv文件。部分代码如下。
public function exportBatCsv($pager, $fileName,$getDataM,$page_name,$diyparameters)
{
// 设置响应头
header("Content-Type: application/CSV");
header(
"Content-Disposition: attachment; filename=" . $fileName . ".csv"
);
header("Expires: 0");
$nowPage = 1;
$limit = isset($pager['exportPageLimit']) ? $pager['exportPageLimit'] : 5000;
$pager['pageSize'] = $limit;
$recordCount = $pager['recordCount'];
$totalPager = ceil($recordCount/$limit);
$pager['pageCount'] = $totalPager;
$fp = fopen('php://output', 'a');
$exportFunc = $pager['exportAllFunc'];
// 判断一下表头数组是否有数据
if ($pager["exportColumns"] != null
&& count($pager["exportColumns"]) > 0
) {
// 循环写入表头
foreach ($pager["exportColumns"] as $key => $column) {
$head[$key] = iconv("UTF-8", "GBK",$column["title"] );
}
fputcsv($fp, $head);
// 判断表中是否有数据
if ($recordCount > 0) {
for ($nowPage=1; $nowPage <= $totalPager; $nowPage++) {
$pager['nowPage'] = $nowPage;
$_POST['dtGridPager'] = json_encode($pager);
$dataArr = $getDataM->$exportFunc($pager['diyparameters'],1);
// 循环写入表中数据
foreach ($dataArr['datas'] as $record) {
$rs = array();
foreach ($pager["exportColumns"] as $tkey => $column) {
$content = $record[$column["id"]];
// 如果内容未被处理则进行格式化
if (!$pager["exportDataIsProcessed"]) {
$content = $this::formatContent(
$column, $content
);
}
$rs[$tkey] = iconv("UTF-8", "GBK//TRANSLIT//IGNORE",$content );
}
fputcsv($fp, $rs);
unset($rs);
}
ob_flush(); //释放内存
flush();
}
}
}
}
编写完成后,测试环境测试,没问题,上线!然后坑就开始了!!!!!
坑
线上服务器环境:centos6.9+nginx1.14.1+php7.0.32,数据库为线上库的一个直供查询的从库,且没有设置超时。
上线后,执行导出,导出过程中导出部分数据后chrome浏览器即报错'下载失败,网络错误'。开始解决问题
- 首先查看程序日志,程序日志没找到有用错误信息。
- 对上面程序打断点,屏蔽掉fputcsv后,再执行导出报错502。502?难道是超时问题?
- 随机检查nginx和php相关配置,包括 fastcgi_connect_timeout,fastcgi_send_timeout,fastcgi_read_timeout,request_terminate_timeout 以及php.ini中max_execution_time,均已配置为不超时或者超时时间很大,排除超时问题。
- 查看nginx服务日志,发现报错信息recv() failed (104: Connection reset by peer) while reading response header from upstream。意思大概是nginx在接受php返回数据时链接 被重置导致无响应中断。WTF?这是什么鬼?好,有问题百度,然后大部分答案都在说是超时问题,然而超时问题已经在上面检查过,绝无可能。
- 继续查看php-fpm日志,发现php-fpm报错 exited on signal 11 (SIGSEGV) 内核抛出中断信号????WTF,这又是什么鬼?然后发现官方解释为
SIGSEGV --- Segment Fault. The possible cases of your encountering this error are:
1.buffer overflow --- usually caused by a pointer reference out of range.
2.stack overflow --- please keep in mind that the default stack size is 8192K.
3.illegal file access --- file operations are forbidden on our judge system.
意思是无非有三种可能,缓冲区溢出,堆溢出,内存溢出或者操作不合法内存。还是检查程序,因为数据量大,在查询以及输出方面 都有注意释放内存。并且php这种语言几乎不直接操作内存,如果有堆溢出,那问题可能就不在程序或者nginx本身,而在php。旋 即将同样的程序部署到了一套nginx和centos版本相同,配置相同,但是php版本为php7.1的服务器上,执行导出,正常无报 错!!!!至此问题解决。
总结
在较大数据量操作下,PHP7.0版本应该存在一些堆栈或者操作系统缓冲区上面的bug,会导致一些溢出问题。然而我再PHP的更新日志中并没有找到相关问题描述。只是将版本升级到7.1该问题就不复存在。具体哪里出问题可以再用core去追溯核心问题。如果有碰到此类问题的朋友仅供参考