Хочу поделиться своим маленьким опытом в реализации раздачи видео-контента.
Есть сервис, раздает видео-контент для просмотра (т.е. именно скачивание не предусматривается). При этом, весь контент делится на 2 категории:
1 — отдается целиком (файл полностью) / пауза, перемотка впред-назад;
2 — отдается одним «виртуальным файлом» вида [конец первого файла]+[некоторое к-во файлов полностью]+[начало последнего файла]. Формат mpegts, каждый набор закодирован одинаково, поэтому можно просто склеивать части.
Две эти категории отличаются логически (совершенно разные вещи), имеют четко отличающиеся URI и физически расположены в разных местах.
Связка nginx+apache.
Первая категория — банальная раздача контента в nginx с небольшим тюнингом.
Вторая часть — через скрипт apache-php при помощи цикла
где $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:
m_download.php полностью приводить не буду. Основной функционал:
где $fd — указатель на файл. Предварительно, конечно, надо разобрать заголовок HTTP_RANGE, открыть файл и установить в нем смещение.
fpassthru() отдает файл «от текущего до конца». В данной ситуации это вполне подошло. Все проигрыватели корректно воспроизводят.
К моему большому удивлению, именно этот способ отдачи дал нужный мне результат. Очереди к диску нет (точнее используется системная, а с моими SAS-3 12Gb/sec и await<10ms вообще хорошо). Соответственно нет и ожидания обработки запроса. Скорость отдачи файла (если скачивать) — порядка 250 Mbit/s. «Тормоза» у клиентов совсем пропали.
При этом, использование памяти сильно уменьшилось, следовательно больше остается для файлового кеша. Сам скрипт потребляет порядка 0,5 MB приватной памяти при выполнении. Исполняемый код все равно существует в памяти в единственном экземпляре, поэтому его размер не имеет значения.
(это где надо слепить несколько кусков разных файлов) тоже изменилась.
Отказался от связки «echo fread()».
К сожалению, в php нет функции для прямого вывода произвольного куска файла. В fpassthru() нет параметра «сколько выводить», он всегда выводит «до конца».
Попробовал вызов системного dd при помощи passthru().
Т.е.:
И… о чудо! Потребление памяти скриптом чуть больше 0,5 MB, размер буфера могу задать любой (на память не влияет). Скорость отдачи (при буфере 4 MB)… свистит (те же 250 Mbit/s).
Вот такая история. В результате пришлось отказаться от раздачи контента просто nginx. Он используется, но только для перенаправления запросов на php5-fpm.
Если кратко, то мое ИМХО: nginx хорошо отдает статику, но плохо читает с диска.
Никогда бы не подумал, что раздача файлов через скрипт php может оказаться эффективнее.
Ну и добавлю, что искал какой-либо httpd с AIO для range-bytes запросов «из коробки». Вроде бы lighttpd 2й версии может, но версия пока нестабильная… Другого ничего подходящего не нашел.
Итак
Есть сервис, раздает видео-контент для просмотра (т.е. именно скачивание не предусматривается). При этом, весь контент делится на 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й версии может, но версия пока нестабильная… Другого ничего подходящего не нашел.