Pull to refresh

Раздача видео. Засада: nginx или php?

Reading time3 min
Views14K
Хочу поделиться своим маленьким опытом в реализации раздачи видео-контента.

Итак


Есть сервис, раздает видео-контент для просмотра (т.е. именно скачивание не предусматривается). При этом, весь контент делится на 2 категории:

1 — отдается целиком (файл полностью) / пауза, перемотка впред-назад;
2 — отдается одним «виртуальным файлом» вида [конец первого файла]+[некоторое к-во файлов полностью]+[начало последнего файла]. Формат mpegts, каждый набор закодирован одинаково, поэтому можно просто склеивать части.

Две эти категории отличаются логически (совершенно разные вещи), имеют четко отличающиеся URI и физически расположены в разных местах.

Как это предлагалось изначально


Связка nginx+apache.

Первая категория — банальная раздача контента в nginx с небольшим тюнингом.
Вторая часть — через скрипт apache-php при помощи цикла

while(!feof($fp)){
...
echo fread($fp, $buf)
...
}

где $fp — указатель на файл с выполненным fseek() куда нужно.

Что не понравилось


Как оказалось, nginx мало подходит для раздачи статики с большим количеством range-bytes запросов (а именно такие запросы в основном и получаются при онлайн-просмотре). Нет возможности использовать AIO для обслуживания таких запросов. В результате образуется длинная очередь к диску и у клиентов просмотр видео часто сопровождается «тормозами». Задавать большое количество буферов бесполезно — только зря расходовать память.

Пробовал последнюю версию nginx (1.12.2 на текущий момент), и --with-file-aio, и --with-threads, и всякое-разное. Эффекта не получил.

Ну и связка «echo fread()» в php тоже весьма сомнительная. Вывод fread идет в промежуточный буфер php и соответственно скрипт потребляет памяти не меньше этого буфера. Если читать файл маленькими кусочками, то увеличивается нагрузка на CPU и падает скорость отдачи. Если читать большими кусками, то на каждый запрос тратится слишком много памяти.

Что в итоге получилось


Ну, в первую очередь отказался от apache. Вместо него php5-fpm. Это дало существенную прибавку быстродействия (скорости ответа) + снизило потребление памяти.

Первую категорию


контента, ради эксперимента, решил попробовать отдавать своим скриптом.

В nginx:

 location ~* /media/.*\..* {
        fastcgi_pass unix:/var/run/php5-fpm.sock;
        include /etc/nginx/sites-available/fastcgi_params;
        fastcgi_param   SCRIPT_FILENAME     /var/www/m_download.php;
        root /var/www;
        send_timeout 1h;
    }

m_download.php полностью приводить не буду. Основной функционал:

fpassthru($fd);

где $fd — указатель на файл. Предварительно, конечно, надо разобрать заголовок HTTP_RANGE, открыть файл и установить в нем смещение.

fpassthru() отдает файл «от текущего до конца». В данной ситуации это вполне подошло. Все проигрыватели корректно воспроизводят.

К моему большому удивлению, именно этот способ отдачи дал нужный мне результат. Очереди к диску нет (точнее используется системная, а с моими SAS-3 12Gb/sec и await<10ms вообще хорошо). Соответственно нет и ожидания обработки запроса. Скорость отдачи файла (если скачивать) — порядка 250 Mbit/s. «Тормоза» у клиентов совсем пропали.

При этом, использование памяти сильно уменьшилось, следовательно больше остается для файлового кеша. Сам скрипт потребляет порядка 0,5 MB приватной памяти при выполнении. Исполняемый код все равно существует в памяти в единственном экземпляре, поэтому его размер не имеет значения.

Вторая категория


(это где надо слепить несколько кусков разных файлов) тоже изменилась.

Отказался от связки «echo fread()».

К сожалению, в php нет функции для прямого вывода произвольного куска файла. В fpassthru() нет параметра «сколько выводить», он всегда выводит «до конца».

Попробовал вызов системного dd при помощи passthru().
Т.е.:

passthru('/bin/dd status=none if='.$fffilename.' iflag="skip_bytes,count_bytes" skip='.$ffseek.' count='.$buf_size);

И… о чудо! Потребление памяти скриптом чуть больше 0,5 MB, размер буфера могу задать любой (на память не влияет). Скорость отдачи (при буфере 4 MB)… свистит (те же 250 Mbit/s).

Вот такая история. В результате пришлось отказаться от раздачи контента просто nginx. Он используется, но только для перенаправления запросов на php5-fpm.

Если кратко, то мое ИМХО: nginx хорошо отдает статику, но плохо читает с диска.

Никогда бы не подумал, что раздача файлов через скрипт php может оказаться эффективнее.
Ну и добавлю, что искал какой-либо httpd с AIO для range-bytes запросов «из коробки». Вроде бы lighttpd 2й версии может, но версия пока нестабильная… Другого ничего подходящего не нашел.
Tags:
Hubs:
Total votes 21: ↑14 and ↓7+7
Comments62

Articles