俗话说:人丑多作怪。在编程界里面也有很多作怪之物,其中首推buffer.
上一次聊到了tar.gz创建导出的问题,我本以为自己把相关的文件流操作都摸清楚了。没想到当我开心地去研究ip库替换方案和同事们开会的时候,突然技术群里面爆了一句:线上导出文件失败,又是破损文件。
当时我的内心是崩溃的,因为在代码层面 我能解决的都解决了。在ob_clean掉那些混在缓冲区里面的渣滓之后,我的文件流输出应该是没问题的啊。
然后我反复确定了一件事儿,测试环境和我本地都是Ok的,这说明代码本身没问题。线上和测试用的php版本都是5.4.x(不是让人憋屈的php5.3.x),其实也就只剩下php和ngnix的配置问题了。我一方面开始查阅资料,一方面又换了一种思维去思考,是否可以用其他输出方式去做呢?
常见的读出方式有下面几种:
1.readfile()
readfile($downFileName); // 直接读文件内容到缓冲区
2.fread方式
这种方式就是典型的文件流方式去读。google的时候,看到有老外说,如果是比较大的文件,建议还是用这种方式,10240个字节一段一段读,保证文件读取完整。代码如下:
<?php $handle= fopen(‘somefile.txt‘, ‘r‘); while (feof($handle) == false) { $part = fread($handle, 10240); echo $part; } fclose($handle);
这样分段读,或者分段写入,都是比较安全稳妥的做法。其实对于php来说300M以上的文件才算大文件,所以我在线上无法导出完整文件不是因为读取上。
fread()其实还可以直接读完整个文件,eg:echo fread($fp, filesize($somefile));
3.file_get_contents()
这种方式是比较粗暴的,在文件不大的前提下是ok的,文件大了之后可能在读取过程中出错。
然后顺便我又get了新技能,下载好的文件和服务器上的文件可以对比md5,使用md5_file()函数对比。
总之我反复折腾代码,一无所获,我测试环境每一种写法都是ok的,一到线上就挂了。后面我查到了php.ini里面可以设置output_buffering,设置成On就是不限制。但是在线上设置后并没有什么卵用,且线上和测试环境在这个设置上是一样的,问题也不在这里。
其实不怕调试,就怕问题不好重现或者说只能在线上重现。时间很快就到了下班,女票打电话给我,我特么郁闷,本来想和她去玩命地enjoy life,结果要留下来陪pm一起debug online(公司没有运维,pm兼任了)。
各种倒腾之后,我又一次提出应该是ngnix的问题,是不是buffer设置太小,每次无法输出完整。pm表示反对,ngnix没问题。后来改用其他文件去尝试着下载,开始比较小的文件如40kb的都是ok的,一旦到了50kb,线上环境就傲娇地拒绝完整输出了。最后我明智地离开了公司,我很明确地知道不是code error,我不能一直耗在一个自己不能掌控的事儿上(线上我没权限搞)。
在我和女票欢乐地吃饭的时候,qq里面传来pm解决问题的message.
果不其然是ngnix的设置问题,buffer设置只有4k,而线下测试环境设施的是128k。
作怪的buffer,折腾之后反而能平静地对待了。