Почему readfile() исчерпывает память PHP?

Я видел много вопросов о том, как эффективно использовать PHP для загрузки файлов, а не для прямого HTTP-запроса (чтобы защитить файлы, отслеживать загрузки и т.д.).

Ответ почти всегда PHP readfile().

НО, хотя он отлично работает во время тестирования с огромными файлами, когда он на живом сайте с сотнями пользователей, загрузки начинают зависать, а пределы памяти PHP исчерпаны.

Итак, как же работает readfile(), что приводит к тому, что память настолько сильно взрывается, когда трафик высок? Я думал, что он должен обойти тяжелое использование памяти PHP, написав непосредственно в выходной буфер?

EDIT: (Чтобы уточнить, я ищу "почему", а не "что я могу сделать". Я думаю, что Apache mod_xsendfile - лучший способ обойти)

Ответ 1

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

PHP должен прочитать файл и записать в выходной буфер. Итак, для файла 300 МБ, независимо от того, какую версию вы написали (по многим небольшим сегментам или по 1 большой кусок), PHP должен в конечном итоге прочитать 300 МБ файла.

Если несколько пользователей должны загрузить файл, возникнет проблема. (На одном сервере хостинг-провайдеры ограничивают память, предоставляемую каждому пользователю хостинга. В такой ограниченной памяти использование буфера не будет хорошей идеей.)

Я думаю, что использование прямой ссылки для загрузки файла - гораздо лучший подход для больших файлов.

Ответ 2

Если у вас есть буферизация вывода, чем использование ob_end_flush() прямо перед вызовом readfile()

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

Ответ 3

Вы можете вообще отключить буферизацию вывода для этого конкретного местоположения, используя директиву PHP output_buffering configuration.

Пример Apache:

<Directory "/your/downloadable/files">
     ...
     php_admin_value output_buffering "0"
     ...
</Directory>

"Выключить", поскольку значение, похоже, тоже работает, в то время как оно действительно должно вызывать ошибку. По крайней мере, согласно как другие типы преобразуются в booleans в PHP. * Пожимает плечами *

Ответ 4

Придумал эту идею в прошлом (как часть моей библиотеки), чтобы избежать использования высокой памяти:

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 );
}

Возможно, это может дать вам некоторое вдохновение.

Ответ 6

Ну, это интенсивная память. Я бы подключил пользователей к статическому серверу, на котором установлено определенное правило для управления загрузками вместо использования readfile().

Если это не опция, добавьте больше ОЗУ для удовлетворения нагрузки или введите систему очередей, которая изящно контролирует использование сервера.