2011-07-08 63 views
21

我见过很多关于如何高效地使用PHP下载文件而不是直接HTTP请求(保持文件安全,跟踪下载等)的问题。为什么readfile()会耗尽PHP内存?

答案几乎总是PHP readfile()

,但尽管它与大文件测试,当它的上线的站点有几百个用户中的伟大工程,下载开始悬挂和PHP内存限制已经耗尽。

那么readfile()的工作原理是什么,导致内存在流量很高时爆炸如此糟糕?我认为它应该通过直接写入输出缓冲区来避免大量使用PHP内存?

编辑:(为了澄清,我正在寻找一个“为什么”,而不是“我能做些什么”我认为Apache的mod_xsendfile是规避最好的办法)

+1

你有PHP输出缓冲(通过'ob_start()')吗?如果是这样,PHP将捕获文件内容并导致内存问题。 – ceejayoz

+1

在链接的PHP文档中使用示例,我没有 - 我有ob_clean();冲洗(); ReadFile的($文件); – tmsimont

回答

4
Description 
int readfile (string $filename [, bool $use_include_path = false [, resource $context ]]) 
Reads a file and writes it to the output buffer*. 

PHP有读取文件并写入输出缓冲区。 因此,对于300Mb的文件,不管你写的是什么(通过很多小段,或者一个大块),PHP最终都要读取300Mb的文件。

如果多个用户必须下载该文件,则会出现问题。 (在一台服务器上,托管服务提供商将限制给每个托管用户的内存,使用这种有限的内存,使用缓冲区并不是一个好主意。)

我认为使用直接链接下载文件是大文件更好的方法。

+0

因此,如果PHP正在为UserA“写入输出缓冲区” - 它使用的内存对UserB和UserC是不可用的?导致最终过度使用记忆? – tmsimont

+0

是的。由于PHP不知道其他用户何时会下载相同的文件,因此PHP只会读取文件以缓冲并尽快将其写入请求者。如果100个用户请求相同的10MB文件,则内存使用率将会可能是1000MB。 – user482594

+0

对我来说是有意义的...所以readfile()并不完全适用,在高流量情况下应该避免。谢谢。 – tmsimont

0

那么,这是内存密集型功能。我会将用户转到一个静态服务器,该静态服务器具有特定的规则集以控制下载,而不是使用readfile()。

如果这不是一个选项,增加更多的RAM来满足负载或引入排队系统,优雅地控制服务器的使用。

2

如果您有与调用前使用权ob_end_flush()函数来READFILE输出缓冲()

header(...); 
ob_end_flush(); 
@readfile($file); 
+0

我试图在2GB文件上做readfile之前添加它,但它仍然超出了内存限制 – NDM

+0

如上所述,输出缓冲不是问题。 – reggie

1

想出了这个主意,在过去(我的库的一部分)以避免高内存使用:

function suTunnelStream($sUrl, $sMimeType, $sCharType = null) 
{ 
    $f = @fopen($sUrl, 'rb'); 
    if($f === false) 
    { return false; } 

    $b = false; 
    $u = true; 

    while($u !== false && !feof($f)) 
    { 
    $u = @fread($f, 1024); 
    if($u !== false) 
    { 
     if(!$b) 
     { $b = true; 
     suClearOutputBuffers(); 
     suCachedHeader(0, $sMimeType, $sCharType, null, !suIsValidString($sCharType)?('content-disposition: attachment; filename="'.suUniqueId($sUrl).'"'):null); 
     } 
     echo $u; 
    } 
} 

    @fclose($f); 
    return ($b && $u !== false); 
} 

也许这可以给你一些灵感。

1

正如这里提到的:"Allowed memory .. exhausted" when using readfile,php文件顶部的以下代码块为我做了窍门。

这将检查php输出缓冲是否有效。如果是这样,它会关闭它。

if (ob_get_level()) { 
    ob_end_clean(); 
} 
+0

由于问题是“为什么”,我认为你应该提供一个解释而不是解决方案。毕竟,你基本上是复制现有的答案。 –

+0

啊是的。我继续前进并添加了一个简短的解释。 –