LAMP:如何为用户动态创建.Zip大型文件,无需磁盘/ CPU抖动

时间:2010-12-05 02:44:32

标签: php bash zip pipe lamp

Web服务通常需要压缩几个大文件以供客户端下载。最明显的方法是创建一个临时zip文件,然后将echo创建给用户或将其保存到磁盘并重定向(将来某个时间删除它)。

然而,这样做有缺点:

  • 密集型CPU和磁盘颠簸的初始阶段,导致...
  • 准备存档时对用户的相当大的初始延迟
  • 每个请求的内存占用非常高
  • 使用大量临时磁盘空间
  • 如果用户取消下载中途,初始阶段(CPU,内存,磁盘)中使用的所有资源都将被浪费

ZipStream-PHP这样的解决方案通过将数据逐个文件铲入Apache文件来改进这一点。但是,结果仍然是高内存使用率(文件完全加载到内存中),以及磁盘和CPU使用率的大幅飙升。

相反,请考虑以下bash片段:

ls -1 | zip -@ - | cat > file.zip
  # Note -@ is not supported on MacOS

此处,zip以流模式运行,导致内存占用量低。管道具有整数缓冲区 - 当缓冲区已满时,OS会暂停写入程序(管道左侧的程序)。这样可以确保zip的工作速度与cat的输出速度一样快。

然后,最佳方式是执行相同的操作:将cat替换为Web服务器进程,将流式 zip文件发送给用户,并将其动态创建。与仅流式传输文件相比,这将产生很少的开销,并且会有一个没有问题的,非尖峰的资源配置文件。

如何在LAMP堆栈上实现这一目标?

7 个答案:

答案 0 :(得分:48)

答案 1 :(得分:3)

另一个解决方案是我的Nginx mod_zip模块,专门为此目的编写:

https://github.com/evanmiller/mod_zip

它非常轻量级,不会调用单独的“zip”进程或通过管道进行通信。您只需指向一个脚本,该脚本列出要包含的文件的位置,mod_zip将完成剩下的工作。

答案 2 :(得分:2)

尝试使用大量不同大小的文件实现动态生成的下载我遇到了这个解决方案但我遇到了各种内存错误,例如“允许内存大小为134217728字节耗尽...”。

ob_flush();之前添加flush();后,内存错误消失。

与发送标题一起,我的最终解决方案看起来像这样(只是将文件存储在没有目录结构的zip中):

<?php

// Sending headers
header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="download.zip"');
header('Content-Transfer-Encoding: binary');
ob_clean();
flush();

// On the fly zip creation
$fp = popen('zip -0 -j -q -r - file1 file2 file3', 'r');

while (!feof($fp)) {
    echo fread($fp, 8192);
    ob_flush();
    flush();
}

pclose($fp);

答案 3 :(得分:2)

我上周末写了这个s3 steaming文件拉链微服务 - 可能有用:http://engineroom.teamwork.com/how-to-securely-provide-a-zip-download-of-a-s3-file-bundle/

答案 4 :(得分:1)

根据the PHP manualthe ZIP extension提供了一个zip:wrapper。

我从来没有使用它,我不知道它的内部结构,但从逻辑上说它应该可以做你想要的,假设ZIP存档可以流式传输,我不完全确定。< / p>

至于你关于“LAMP堆栈”的问题,只要PHP configured to buffer output就不会有问题。


编辑:我正在尝试将概念验证放在一起,但这似乎并非无足轻重。如果您对PHP的流没有经验,那么即使可能,它也可能过于复杂。


编辑(2):看完ZipStream后重新阅读你的问题,当你说(强调添加)时,我发现你的主要问题是什么

  

有效的Zipping应该以流模式运行,即处理文件并以下载速率提供

这部分将非常难以实现,因为我不认为PHP提供了一种方法来确定Apache缓冲区的完整程度。所以,你的问题的答案是否定的,你可能无法在PHP中做到这一点。

答案 5 :(得分:0)

看来,您可以使用fpassthru()消除任何与输出缓冲区相关的问题。我也使用-0来节省CPU时间,因为我的数据已经很紧凑。我使用此代码来提供整个文件夹,即时压缩:

chdir($folder);
$fp = popen('zip -0 -r - .', 'r');
header('Content-Type: application/octet-stream');
header('Content-disposition: attachment; filename="'.basename($folder).'.zip"');
fpassthru($fp);

答案 6 :(得分:0)

我刚刚在这里发布了用纯PHP用户态编写的ZipStreamWriter类:

https://github.com/cubiclesoft/php-zipstreamwriter

它不使用外部应用程序(例如zip)或诸如ZipArchive之类的扩展,而是通过实现功能强大的ZIP编写器来支持将数据流传入和传出类。

PKWARE ZIP file specification第4.3.5节所述,通过使用ZIP文件格式的“数据描述符”来实现流媒体方面的工作:

4.3.5文件数据后可以跟文件的“数据描述符”。 数据描述符用于促进ZIP文件流传输。

尽管有一些可能需要注意的限制。并非每个工具都能读取流式ZIP文件。另外,对Zip64流式ZIP文件的支持可能更少,但是仅与此类中2GB以上的文件有关。但是,7-Zip和Windows 10内置的ZIP文件阅读器似乎都可以处理ZipStreamWriter类扔给他们的所有疯狂文件。我使用的十六进制编辑器也锻炼得很好。

使用ZipStreamWriter类时,建议在将缓冲区发送到Web服务器之前,允许一次至少建立4KB但不超过65KB的缓冲区。否则,对于许多非常小的文件,您将清除少量的零碎数据,并在Apache回调的最后浪费大量的CPU周期。

当不存在某些内容或我不喜欢现有选项时,我会同时找到正式和非正式的规范,并找到一些可以使用的示例,然后从头开始构建它。即使只是一点点的矫kill过正,这也是解决问题的可靠方法。