Достижение оптимального размера фрагмента «fread»?

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

Это также включает в себя вещи, связанные с сервером, поэтому я не уверен, что Stackoverflow — это правильное место, но это кажется лучшим выбором по сравнению с ServerFault.

Для начала выложу два скриншота:

http://screensnapr.com/e/pnF1ik.png

http://screensnapr.com/e/z85FWG.png

Теперь у меня есть скрипт, использующий PHP для потоковой передачи файлов конечному пользователю. Он использует fopen и fread для потоковой передачи файла. Размер большинства этих файлов превышает 100 МБ. Меня беспокоит то, что иногда вышеприведенная статистика превращается в мою серверную статистику. Два экрана с разных серверов; оба сервера являются выделенными ящиками для потоковой передачи файлов. На них больше ничего не работает, кроме потоковой передачи файла PHP конечному пользователю.

Меня смущает тот факт, что даже когда мой сервер передает конечным клиентам всего около 4 МБ/с данных, скорость чтения с диска составляет 100 МБ/с и выше. Этот безумный уровень ввода-вывода в конечном итоге блокирует мой процессор, потому что он ждет ввода-вывода, и задачи накапливаются; в конце концов мой сервер полностью перестает отвечать на запросы, требуя перезагрузки.

Мой текущий размер фрагмента fread установлен на 8 * 1024. Мой вопрос: поможет ли вообще изменение размера блока и экспериментирование? Клиент загружает данные только со средней скоростью ~ 4 МБ / с. Так почему же диск читает данные со скоростью 100 МБ/с? Я пробовал все возможные решения на стороне сервера; Я даже заменил диски на новые, чтобы исключить возможную проблему с дисками. Мне кажется, что это проблема сценария; может быть, PHP считывает все данные с диска независимо от того, сколько он передает конечному клиенту?

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


person Lifetalk    schedule 18.05.2011    source источник
comment
Возможно, существует несколько одновременных сеансов потоковой передачи файлов. Вам необходимо ограничить максимальное количество одновременных запросов.   -  person Mohamed Nuur    schedule 18.05.2011
comment
Я понимаю. Это может быть проблемой. Но будет ли это по-прежнему создавать такую ​​​​большую разницу между фактическими отправленными данными и данными, считанными с диска?   -  person Lifetalk    schedule 18.05.2011
comment
Проверьте использование памяти с помощью free в командной строке. Посмотрите, не попал ли файл подкачки   -  person Marc B    schedule 19.05.2011


Ответы (3)


8 * 1024 байт? Это кажется вполне разумным, и если это так, ваш высокий дисковый ввод-вывод, вероятно, связан с одновременным запросом. Рассматривали ли вы реализацию какого-либо дросселирования полосы пропускания? Вот реализация только для PHP, которую я сделал для своего фреймворка, phunction:

public static function Download($path, $speed = null, $multipart = false)
{
    if (strncmp('cli', PHP_SAPI, 3) !== 0)
    {
        if (is_file($path) === true)
        {
            while (ob_get_level() > 0)
            {
                ob_end_clean();
            }

            $file = @fopen($path, 'rb');
            $size = sprintf('%u', filesize($path));
            $speed = (empty($speed) === true) ? 1024 : floatval($speed);

            if (is_resource($file) === true)
            {
                set_time_limit(0);
                session_write_close();

                if ($multipart === true)
                {
                    $range = array(0, $size - 1);

                    if (array_key_exists('HTTP_RANGE', $_SERVER) === true)
                    {
                        $range = array_map('intval', explode('-', preg_replace('~.*=([^,]*).*~', '$1', $_SERVER['HTTP_RANGE'])));

                        if (empty($range[1]) === true)
                        {
                            $range[1] = $size - 1;
                        }

                        foreach ($range as $key => $value)
                        {
                            $range[$key] = max(0, min($value, $size - 1));
                        }

                        if (($range[0] > 0) || ($range[1] < ($size - 1)))
                        {
                            ph()->HTTP->Code(206, 'Partial Content');
                        }
                    }

                    header('Accept-Ranges: bytes');
                    header('Content-Range: bytes ' . sprintf('%u-%u/%u', $range[0], $range[1], $size));
                }

                else
                {
                    $range = array(0, $size - 1);
                }

                header('Pragma: public');
                header('Cache-Control: public, no-cache');
                header('Content-Type: application/octet-stream');
                header('Content-Length: ' . sprintf('%u', $range[1] - $range[0] + 1));
                header('Content-Disposition: attachment; filename="' . basename($path) . '"');
                header('Content-Transfer-Encoding: binary');

                if ($range[0] > 0)
                {
                    fseek($file, $range[0]);
                }

                while ((feof($file) !== true) && (connection_status() === CONNECTION_NORMAL))
                {
                    ph()->HTTP->Flush(fread($file, round($speed * 1024)));
                    ph()->HTTP->Sleep(1);
                }

                fclose($file);
            }

            exit();
        }

        else
        {
            ph()->HTTP->Code(404, 'Not Found');
        }
    }

    return false;
}

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

// serve file at 4 MBps (max)
Download('/path/to/file.ext', 4 * 1024);

Вы даже можете быть более щедрым по умолчанию и уменьшить $speed в зависимости от значений, которые вы получаете из первого индекса sys_getloadavg(), чтобы не нагружать процессор.

person Alix Axel    schedule 18.05.2011
comment
Ваша структура выглядит уникальной среди других, которые я видел. Вы указываете шаблон проектирования HMVC. Я думаю, что некоторое время буду изучать ваш код. Спасибо, что открыли. - person daganh; 18.05.2011
comment
@daganh: Спасибо. Однако для него нет документации, этот ответ раскрывает часть его в некоторых (некоторых устаревших) деталях: stackoverflow.com/questions/3023818/. - person Alix Axel; 18.05.2011

Как правило, может случиться так, что фактический ввод-вывод быстрее, чем ввод-вывод пользовательского пространства, из-за предварительной выборки и накладных расходов файловой системы. Однако это никогда не должно блокировать ваш сервер. Размер кеша практически не повлияет на это, если он составляет от 1 КБ до, скажем, 16 МБ. Однако вместо того, чтобы использовать php для потоковой передачи файлов, вам действительно следует рассмотреть гораздо более оптимизированный readfile.

При этом, за исключением серьезной ошибки программирования, такое поведение, вероятно, не имеет прямого отношения к вашему маленькому циклу. Во-первых, вы должны использовать iotop, чтобы узнать, какая программа на самом деле вызывает ввод-вывод. Если это php (сколько одновременных скриптов? Извините, скриншоты кажутся полностью искаженными и не показывают никакой полезной информации), исключите использование буферизации вывода и посмотрите на потребление памяти, а также различные параметры настройки php. (у phpinfo есть хороший обзор). Кстати, htop — гораздо более приятная альтернатива top ;).

person phihag    schedule 18.05.2011
comment
Спасибо за предложения. Я знаю, что не опубликовал достаточно данных, но скриншот был из dstat — он одновременно отслеживал дисковый ввод-вывод, сетевую активность, информацию о ЦП и средние значения нагрузки. Это дает мне хорошее представление о том, что является причиной чего. Сказав это, я использую iotop. Согласно iotop, скачок чтения с диска происходит из-за Apache/httpd (PHP работает как модуль apache). Так что это определенно это. Я также исключил буферизацию вывода, спасибо за предложение :) И мне не о чем беспокоиться об использовании памяти - обычно 5-6 ГБ свободно из 8 ГБ общей системной памяти. - person Lifetalk; 18.05.2011
comment
@Lifetalk, скриншоты просто показывают систему в целом. Насколько нам известно, у вас может быть другой процесс, вызывающий эту медлительность. Вы действительно должны включать данные для каждого процесса, собранные с помощью (h)top и iotop. - person phihag; 18.05.2011

Теперь у меня есть скрипт, использующий PHP для потоковой передачи файлов конечному пользователю.

Просто чтобы прояснить, что на самом деле происходит, Apache отвечает за фактический «поток». PHP имеет дело непосредственно с Apache для его вывода. Поэтому вашим конечным пользователем PHP-скрипта является Apache. Затем Apache обрабатывает вывод для пользователя, который, по-видимому, в вашем случае составляет около 4 МБ/сек. Apache, однако, не имеет этого ограничения и может принимать все ваши выходные данные сразу, а затем обрабатывать отложенную доставку клиенту. Чтобы доказать это, вы должны увидеть завершение своего скрипта до того, как поток будет доставлен. Если ваш скрипт разворачивается и пытается доставить другой файл, то вы ставите Apache в очередь против ресурсов вашего сервера.

Лучшее решение может заключаться в том, чтобы позволить Apache полностью обрабатывать доставку файлов, позволяя пользователю запрашивать загрузку с доступного URL-адреса. Очевидно, что это ограничено статическим контентом. Чтобы исправить приведенный выше сценарий, потребуется отложить чтение некоторых файлов, чтобы Apache мог доставлять фрагменты вместо буферизации всего вывода.

РЕДАКТИРОВАТЬ: Если с вашей памятью все в порядке, и мы можем исключить активность диска подкачки, то это могут быть просто одновременные запросы на чтение файлов. Если мы запросим 5 файлов по 100 МБ, это будет 500 МБ чтения. Apache не будет ограничивать ваш скрипт и фактически буферизует весь вывод, который может превышать 100 МБ за раз. Это будет учитывать большую активность дискового ввода-вывода, потому что каждый запрос приводит к чтению всего файла в буфер. Использование дросселя, предложенного Аликс, позволит выполнять больше одновременных запросов, но в конечном итоге вы достигнете предела. Мы не можем быть уверены, насколько быстро пользователь получает данные от Apache, поэтому вам, возможно, придется найти хороший баланс для размера дроссельной заслонки, чтобы Apache и PHP могли работать с фрагментами ваших файлов, а не с целым файлом.

person daganh    schedule 18.05.2011
comment
Я бы понял это, если бы его проблема была связана с использованием памяти, но дисковый ввод-вывод? - person Alix Axel; 18.05.2011
comment
Он как бы подразумевает, что может быть несколько запросов на файлы размером более 100 МБ, и если скрипт выгружает этот файл в apache, вполне возможно, что память быстро съедается, и сервер запускает диск подкачки для обработки буферизованных файлов в памяти. Это может быстро привести к тому, что, казалось бы, блокирует сервер. - person daganh; 18.05.2011
comment
Его комментарий к ответу @phihag: мне не о чем беспокоиться об использовании памяти - обычно 5-6 ГБ свободно. - person Alix Axel; 18.05.2011
comment
@ Аликс, я только что это заметил. Добавлено еще одно объяснение, разъясняющее мое мнение, что это должно быть параллельное чтение. Чтение файлов такого размера значительно увеличивает дисковый ввод-вывод. Сделайте это одновременно несколько раз, и у вас есть проблема. Ваш дроссель кажется лучшим подходом к решению этой проблемы. Голосование. - person daganh; 19.05.2011