协作翻译
原文:How to Read Big Files with PHP (Without Killing Your Server)
链接:https://www.sitepoint.com/performant-reading-big-files-php/
译者:Tocy, Tony, 南宫冰郁, Tot_ziens
做为PHP研发人员,咱们并不经常需要担心内存管理。PHP 引擎在咱们背面做了很好的清理工作,短期执行上下文的 Web 服务器模型寓意着即使是最潦草的代码亦不会导致持久的影响。
很少状况下咱们可能需要走出这个舒适的地区 ——例如当咱们试图在一个大型项目上运行 Composer 来创建我们能够创建的最小的 VPS 时,或当咱们需要在一个一样小的服务器上读取大文件时。
后面的问题便是咱们将在本教程中深入探讨的。
在 GitHub(https://github.com/sitepoint-editors/sitepoint-performant-reading-of-big-files-in-php) 上能够找到本教程的源码。
衡量成功的标准
保证咱们对代码有改进的独一办法是测试一个欠好的状况,而后将咱们修复之后的测绘与另一个进行比较。换句话说,除非咱们晓得“处理方法”对咱们有多大的帮忙(倘若有的话),否则咱们不晓得它是不是真的是一个处理方法。
这儿有两个咱们能够关系的衡量标准。首要是CPU运用率。咱们要处理的进程有多快或多慢?第二是内存运用状况。脚本执行时需要多少内存?这两个一般是成反比的 - 这寓意着咱们能够以CPU运用率为代价来降低内存运用,反之也然。
在一个异步执行模型(如多进程或多线程的PHP应用程序)中,CPU和内存的运用率是很重要的考量原因。在传统的PHP架构中,当任何一个值达到服务器的极限时,这些一般都会作为问题。
测绘PHP内的CPU运用率是不切实质的。倘若这是你要关注的行业,请思虑在Ubuntu或MacOS上运用类似top的工具。针对Windows,请思虑运用Linux子系统,以便在Ubuntu中运用top。
为了本教程的目的,咱们将测绘内存运用状况。咱们将瞧瞧在“传统”的脚本中运用了多少内存。
咱们将执行有些优化策略并对其进行度量。最后,我期盼你能够做出一个有经验的选取。
咱们查看内存运用多少的办法是:
咱们将在脚本的最后运用这些函数,以便咱们能够看到哪个脚本一次运用最大的内存。
咱们的选取是什么?
这儿有非常多办法能够有效地读取文件。然则亦有两种咱们可能运用它们的情况。咱们想要同期读取和处理所有数据,输出处理过的数据或按照咱们所读取的内容执行其他操作。咱们亦可能想要转换一个数据流,而不需要真正拜访的数据。
让咱们设想一下,针对第1种状况,咱们期盼读取一个文件,并且每10,000行创建一个独立排队的处理作业。咱们需要在内存中保存最少10000行,并将它们传递给排队的工作管理器(无论采取何种形式)。
针对第二种状况,咱们假设咱们想要压缩一个尤其大的API响应的内容。咱们不在乎它的内容是什么,但咱们需要保证它是以压缩形式备份的。
在这两种状况下,倘若咱们需要读取大文件,首要,咱们需要晓得数据是什么。第二,咱们并不在乎数据是什么。让咱们来探索这些选取吧...
逐行读取文件
有许多操作文件的函数,咱们把部分结合到一个简单的文件阅读器中(封装为一个办法):
咱们读取一个文本文件为莎士比亚全集。文件体积为5.5MB,内存占用峰值为12.8MB。此刻让咱们用一个生成器来读取每一行:
文本文件体积不变,但内存运用峰值只是393KB。即使咱们能把读取到的数据做有些事情亦并不寓意着什么。亦许咱们能够在看到两条空白时把文档分割成块,像这般:
猜到咱们运用了多少内存吗?咱们把文档分割为1216块,仍然只运用了459KB的内存,这是不是让你惊讶?思虑到生成器的性质,咱们运用的最多内存是运用在迭代中咱们需要存储的最大文本块。在本例中,最大的块为101985字符。
我已然撰写了运用生成器提示性能和Nikita Popov的迭代器库,倘若你感兴趣就去瞧瞧吧!
生成器还有其它用途,然则最显著的好处便是高性能读取大文件。倘若咱们需要处理这些数据,生成器可能是最好的办法。
管道间的文件
在咱们不需要处理数据的状况下,咱们能够把文件数据传递到另一个文件。一般被叫作为管道(大概是由于咱们看不到除了两端的管子里面,当然,它亦是不透明的),咱们能够经过运用流办法实现。让咱们先写一个脚本从一个文件传到另一个文件。这般咱们能够测绘内存的占用状况:
不出所料,这个脚本运用更加多的内存来进行文本文件复制。这是由于它读取(和保存)文件内容在内存中,直到它被写到新文件中。针对小文件这种办法亦许没问题。当为更大的文件时,就捉襟见肘了…
让咱们尝试用流(管道)来传送一个文件到另一个:
这段代码稍微有点陌生。咱们打开了两文件的句柄,第1个是只读模式,第二个是只写模式,而后咱们从第1个复制到第二个中。最后咱们关闭了它,亦许使你惊讶,内存只占用了393KB。
这似乎很熟练。像代码生成器在存储它读到的每一行代码?那是由于第二个参数fgets规定了每行读多少个字节(默认值是-1或直到下一行径止)。
第三个参数stream_copy_to_stream和第二个参数是同一类参数(默认值相同),stream_copy_to_stream一次从一个数据流里读一行,同期写到另一个数据流里。它跳过生成器仅有一个值的部分(由于咱们不需要这个值)。
这篇文案针对咱们来讲可能是没用的,因此让咱们想有些咱们可能会用到的例子。假设咱们想从咱们的CDN中输出一张照片,做为一种重定向的路由应用程序。咱们能够参照下边的代码来实现它:
设想一下,一个路由应用程序让咱们看到这段代码。然则,咱们想从CDN获取一个文件,而不是从本地的文件系统获取。咱们能够用有些其他的东西来更好的替换file_get_contents(就像Guzzle),即使在引擎内部它们几乎是同样的。
照片的内存大概有581K。此刻,让咱们来试试这个
内存运用显著变少(大概400K),然则结果是同样的。倘若咱们不关注内存信息,咱们依旧能够用标准模式输出。实质上,PHP供给了一个简单的方式来完成:
其它流
还有其它有些流,咱们能够经过管道来写入和读取(或只读取/只写入):
php://stdin (只读)
php://stderr (只写, 如php://stdout)
php://input (只读) 这使咱们能够拜访原始请求体
php://output (只写) 让咱们写入输出缓冲区
php://memory 和 php://temp (读-写) 是咱们能够临时存储数据的地区。 区别之处在于一旦它变得足够大 php://temp 会将数据存储在文件系统中,而 php://memory 将始终持存储在内存中直到资源耗尽。
过滤器
还有一个咱们能够在stream上运用的技巧,叫作为过滤器。它们是一种中间的过程,供给对stream数据的有些掌控,但不把她们暴露给咱们。想象一下,咱们会运用Zip扩展名来压缩咱们的shakespeare.txt文件。
这是一小段整洁的代码,但它测绘内存占用在10.75MB上下。运用过滤器的话,咱们能够减少内存:
此处,咱们能够看到名为php://filter/zlib.deflate的过滤器,它读取并压缩资源的内容。咱们能够在之后将压缩数据导出到另一个文件中。这仅运用了896KB.
我晓得这是不同样的格式,或制作zip存档是有好处的。你不得不可疑:倘若你能够选取区别的格式并节省约12倍的内存,为何不选呢?
为认识压此数据,咱们能够经过执行另一个zlib filter将压缩后的数据还原:
Streams have been extensively covered in Stream在“理解PHP中的流”和“U有效运用PHP中的流
”中已然被全面介绍了。倘若你爱好一个完全区别的视角,能够阅读一下。
定制流
fopen和file_get_contents有它们自己的一套默认选项,然则这些都是完全可定制的。为了定义它们,咱们需要创建一个新的流上下文:
在这个例子中,咱们正在尝试向API发出POST请求。 API终端是安全的,但咱们仍然需要运用http上下文属性(用于http和https)。咱们设置有些信息头参数,并打开一个文件句柄到API。因为上下文处理写操作,咱们能够将句柄打开为只读。
查看文档认识更加多:https://php.net/function.stream-context-create
制定自定义协议和过滤器
在咱们结束之前,让咱们谈谈制定自定义协议。 倘若你查看文档,你能够找到一个示例类来实现:
咱们不打算实现其中的一个,由于我认为它应该有自己的教程。这儿有很多工作需要完成。然则一旦这个工作完成,咱们能够很容易地注册咱们的流包装:
一样,亦能够创建自定义流过滤器。该文档有一个示例过滤器类:
这能够很容易地注册:
明显表示名叫作需要匹配新的筛选器类的filtername属性。亦能够在php://filter/highligh-names/resource=story.txt字符串中运用自定义过滤器。定义过滤器比定义协议要容易得多。由于协议需要处理目录操作,而过滤器只需处理每一个数据块。 倘若你有这个想法,我剧烈意见你尝试创建自定义协议和过滤器。倘若你能够将过滤器应用于stream_copy_to_streamoperations,那样即使在运用大容量文件时,你的应用程序亦能够在无内存的状况下运用。想象一下,编写一个调节体积的图像过滤器或加密的应用程序过滤器。
总结
虽然这不是咱们经常遇到的问题,但在处理大文件时很容易搞砸。在异步应用程序中,当咱们不重视小心运用内存的话,很容易引起全部服务器宕机。
本教程期盼向你介绍有些新的想法(或让你重新认识她们),以便你可以更加多地思虑怎样有效地读取和写入大型文件。当咱们起始熟练流程和生成器,并停止运用像file_get_contents这般的函数时,咱们的应用程序中就会减少错误的类别,这看起来是很好。
举荐阅读
怎样愉快的运用 MQ - 详述各样功能场景
必须 SQL 查找优化技巧,提高网站拜访速度
点击“阅读原文”查看更加多精彩内容
|