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

Итак


Есть сервис, раздает видео-контент для просмотра (т.е. именно скачивание не предусматривается). При этом, весь контент делится на 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й версии может, но версия пока нестабильная… Другого ничего подходящего не нашел.

Комментарии (62)


  1. Waki
    28.11.2017 08:49

    Интересный эксперимент. Думаю сейчас придут люди которые хорошо разбираются с тюнингом nginx и расскажут почему у вас с ним ничего не получилось :)


    1. zuborg
      28.11.2017 10:43

      Гадание без конфигов — неблагодарное дело.


  1. XaosSintez
    28.11.2017 09:04
    +1

    Я бы предположил, что Вам нужен nginx модуль для pseudo streaming


    1. Yagoda123 Автор
      28.11.2017 09:24

      Смотрел это дело, но отказался. Контент в первой категории всяких-разных форматов. Воспроизведение предусматривается на (в том числе) старых железяках (типа ТВ-приставки), поэтому надеяться на URL c ?start=nn не приходится. А значит будет блокирующее чтение диска.
      Чем мне еще понравилась получившаяся схема — один файловый кеш, системный. И он будет использовать всю доступную память, уменьшаясь при необходимости. Если же часть запросов (т.е. чтений диска) будут кешироваться в nginx, а другая в системном кеше, то это лишнее дублирование. Памяти, думаю, потребует больше.
      Кстати, при некоторых настройках nginx (много и большие буферы) он у меня отжирал почти всю память, даже свопиться система начинала.


  1. Jokerjar
    28.11.2017 09:28

    Попробуйте сделать что-то типа:

    worker_rlimit_nofile 16384;
    thread_pool moovies threads=8 max_queue=65536;
    ...
    sendfile on;
    sendfile_max_chunk 1m;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    server_tokens off;

    open_file_cache max=100000 inactive=30s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;
    ...
    location ~ \.mp4$ {
    aio threads=moovies;
    directio 16M;
    output_buffers 2 1M;
    }


    Подобные настройки дают отличный результат на высоконагруженном тьюб-сайте. Также стоит потюнинговать sysctl.


    1. Yagoda123 Автор
      28.11.2017 09:39

      Хм… Надо именно такие параметры попробовать.
      Похожее (но не совсем так) делал, ничего хорошего не получил. Только памяти nginx съел много.
      В документации: «Невыравненный конец файла будет читаться блокированно. То же относится к запросам с указанием диапазона запрашиваемых байт (byte-range requests)». По моим оценкам так и получается.


      1. Yagoda123 Автор
        28.11.2017 10:13

        Добавлю. Весь контент у меня находится на 8ми SAS-3 12Gbit/s дисках.
        Читать все через «прокладку» mhddfs/aufs, по моим оценкам, «не гуд». Во всяком случае, тесты показали скорость чтения на много меньше чем читать напрямую.
        Поэтому все запросы rewrit(ом) приводил к нужному мне виду и переключался на нужный диск при помощи try_files. До некоторых пор это помогало. Но трафик подрос, клиентов онлайн стало больше и появились «тормоза». Вот и начал экспериментировать.


        1. Jokerjar
          28.11.2017 11:10

          У меня на серверах с подобной задачей все упирается в скорость чтения с дисков или производительность канала связи. В остальном, Nginx отлично справляется с настроенным aio, как я описывал выше, и тюнингом sysctl (тут уже индивидуально нужно тестировать). Если и использовать скрипты в схеме с отдачей видео, то только с x-accel-redirect, как я считаю. На счет второго пункта, можно подготавливать склеенный файл (на ssd или tmpfs) и x-accel-redirect'ить на него (продумав при этом периодическую чистку).


          1. Yagoda123 Автор
            28.11.2017 11:27

            У меня в диски и канал пока не упирается. Канал 10 Gb.
            По факту в выходные было 850 Mb. На используемых дисках await выше 10 ms не поднимается. Обычно 2 — 5.
            Склеивать у меня не выход. Надо уметь отдавать произвольную часть файла. проблема именно в этом. И в том, что практически все запросы такие.


          1. jtiq
            28.11.2017 12:27

            А можно пример настроек sysctl?


            1. Yagoda123 Автор
              28.11.2017 12:30

              Какие именно интересуют?
              Все постить много…


              1. jtiq
                28.11.2017 14:32

                Ну хотя бы основные, что стоит поменять после запуска сервера с HDD дисками и 1 гбит каналом.


                1. Yagoda123 Автор
                  29.11.2017 02:22

                  #uname -a
                  Linux backup 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt20-1+deb8u2 (2016-01-02) x86_64 GNU/Linux

                  #cat /etc/debian_version
                  8.3

                  Добавлено в sysctl.conf:

                  net.ipv4.tcp_keepalive_time = 180
                  net.netfilter.nf_conntrack_tcp_timeout_established = 3000
                  net.netfilter.nf_conntrack_tcp_timeout_close_wait = 10
                  net.netfilter.nf_conntrack_generic_timeout = 300
                  net.netfilter.nf_conntrack_tcp_timeout_syn_sent = 60
                  net.netfilter.nf_conntrack_tcp_timeout_syn_recv = 60
                  net.netfilter.nf_conntrack_tcp_timeout_established = 300
                  net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 10
                  net.netfilter.nf_conntrack_tcp_timeout_close_wait = 10
                  net.netfilter.nf_conntrack_tcp_timeout_last_ack = 30
                  net.netfilter.nf_conntrack_tcp_timeout_time_wait = 60
                  net.netfilter.nf_conntrack_tcp_timeout_close = 10
                  net.netfilter.nf_conntrack_tcp_timeout_max_retrans = 120
                  net.netfilter.nf_conntrack_tcp_timeout_unacknowledged = 120
                  net.netfilter.nf_conntrack_udp_timeout = 30
                  net.netfilter.nf_conntrack_udp_timeout_stream = 180
                  net.netfilter.nf_conntrack_icmp_timeout = 10
                  net.netfilter.nf_conntrack_events_retry_timeout = 15
                  net.ipv4.netfilter.ip_conntrack_generic_timeout = 120
                  net.ipv4.netfilter.ip_conntrack_tcp_timeout_syn_sent = 60
                  net.ipv4.netfilter.ip_conntrack_tcp_timeout_syn_sent2 = 60
                  net.ipv4.netfilter.ip_conntrack_tcp_timeout_syn_recv = 60
                  net.ipv4.netfilter.ip_conntrack_tcp_timeout_established = 300
                  net.ipv4.netfilter.ip_conntrack_tcp_timeout_fin_wait = 10
                  net.ipv4.netfilter.ip_conntrack_tcp_timeout_close_wait = 10
                  net.ipv4.netfilter.ip_conntrack_tcp_timeout_last_ack = 30
                  net.ipv4.netfilter.ip_conntrack_tcp_timeout_time_wait = 60
                  net.ipv4.netfilter.ip_conntrack_tcp_timeout_close = 10
                  net.ipv4.netfilter.ip_conntrack_tcp_timeout_max_retrans = 120
                  net.ipv4.netfilter.ip_conntrack_udp_timeout = 30
                  net.ipv4.netfilter.ip_conntrack_udp_timeout_stream = 180
                  net.ipv4.netfilter.ip_conntrack_icmp_timeout = 10
                  net.ipv4.ipfrag_time = 5
                  net.ipv4.tcp_timestamps = 1
                  net.ipv4.ipfrag_time = 1
                  vm.swappiness=20
                  vm.vfs_cache_pressure=500


                  1. jtiq
                    29.11.2017 10:19

                    Спасибо, посмотрю что и за что отвечает


      1. symbix
        28.11.2017 10:36

        Надо тюнить настройки тредпулов и смотреть, во что упирается (возможно, в sysctl-и).


        Еще есть радикальный вариант — перейти на FreeBSD. :-)


        1. Yagoda123 Автор
          28.11.2017 10:44

          Игрался с thread_pool. Даже несколько делал (на каждый диск свой). Не особо помогало.
          Еще осложнило эксперименты то, то все это происходит на боевом сервере. Лишний раз клиентов дергать не хочется. А сымитировать такую нагрузку сложно.
          // Еще есть радикальный вариант — перейти на FreeBSD. :-)
          Да, это уже понял. Там AIO вроде без проблем. Но ради одного сервиса изучать FreeBSD???
          Хотя, если «упремся», возможно так и придется.


          1. symbix
            28.11.2017 11:44

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


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


          1. TrueRedRat
            28.11.2017 11:56

            Но ради одного сервиса изучать FreeBSD???

            Фряха — как велосипед (в хорошем смысле этого слова): сел и поехал. Все системные настройки находятся в паре файлов в /etc, все пользовательские — в /usr/local/etc. При небольшом навыке установка и настройка нового сервера занимает считанные минуты. Очень user-friendly система.


            1. symbix
              28.11.2017 14:42

              Не во всем. Пакетный менеджер очень специфический, а недавно его вообще не было (и это, полагаю, основная причина потери приличной до этого доли рынка). Плюс всякая местная специфика типа "чтобы узнать настоящий размер бэклога, надо умножить циферку на 1.5" :-)


    1. xXxSPYxXx
      28.11.2017 22:55

      Сколько траффика отдаете?


      1. Yagoda123 Автор
        29.11.2017 02:25

        Интерфейс 10Gb. Зафиксированный максимум 850 Mb. На тестах (1 URL * 200 потоков) выдал 2,8 Gb.


        1. xXxSPYxXx
          29.11.2017 11:15

          Вопрос был Jokerjar.
          Вам я бы посоветовал привести видео к одному формату и стримить через DASH, HDS, HLS, MSS. Конечно нужно смотреть что клиенты поддерживают. Ну и железо слабовато для утилизации 10G канала.


          1. Yagoda123 Автор
            29.11.2017 12:40

            Приводить к одному формату, к сожалению, невозможно. Там реально зоопарк.

            По железу. Нет цели «забить весь канал». Но думаю, 2-3 Gb в итоге потребуется.
            CPU хватит за глаза. Диски… 8 штук, каждый гарантированно выдаст 50 МБ/с. В реальности больше (dd if=xxx.avi of=/dev/null выдает всегда больше 100 МБ/с). Итого в битах 8*50*8=3200 Мб.
            Памяти бы добавить для кеша — это планируется.


            1. xXxSPYxXx
              29.11.2017 13:26

              Попробуйте померять скорость так hdparm -Tt /dev/sda

              Почему не возможно? Видео каждый раз приходит в разных форматах? Можно сделать автоматическую конвертацию. Но это будет оправдано, если клиенты смогут в HLS. Возможно стоит клиентов которые поддерживают, перевести на HLS, остальным отдавать файл сразу?


              1. Yagoda123 Автор
                30.11.2017 03:22

                Timing cached reads: 15548 MB in 2.00 seconds = 7780.56 MB/sec
                Timing buffered disk reads: 624 MB in 3.00 seconds = 207.74 MB/sec

                Конвертировать неоправданно. «Продвинутых» клиентов сильно мало.


            1. Jokerjar
              30.11.2017 04:27

              Каким образом у вас зоопарк просматривают «онлайн»? Для веба (и каждого браузера) есть определенный конкретный список форматов. Как, допустим, проиграть онлайн wmv?


              1. Yagoda123 Автор
                30.11.2017 04:47

                Хм… Хотя отдача и по http, но это не Веб. Т.е. клиенты не используют какой-либо браузер. Используются специализированные «железяки» или специализированный софт (ПК, Smart-TV, Android). Список доступного контента и ссылки на него получают тоже через http с другого сервера. Этот просто раздает.

                Каким образом зоопарк?.. Так сложилось. Такова жизнь. Слава богу клиенты этот зоопарк понимают. Хм… wmv вроде не понимают, но такого и не видел.

                Вообще-то написал тут не особо заморачиваясь на видео.
                Есть задача: раздавать произвольные части больших бинарных файлов. Так уж получилось, что это видео. В принципе, без разницы что это. Ну и как эту задачу лучше выполнить.

                Всегда считал, что nginx для этого «самое то». Ну и когда уперся, начал экспериментировать. Получилось, что раздавать части файлов эффективнее через fastcgi. И даже php не принципиально, использовал что было «под рукой».

                Т.е. связка nginx (без буферизации) — fastcgi оказалась эффективной для такой задачи.
                Ну и решил поделиться своим удивлением ))


          1. Jokerjar
            30.11.2017 04:26

            Сервера по гигабиту, отдают в пик примерно 937 мегабит, упирается в скорость чтения с дисков (обычные HDD 4Tb).


            1. Yagoda123 Автор
              30.11.2017 04:59

              Ну, у меня не совсем обычные HDD.
              SAS-3, 12Gb/s, контроллер соответствующий.
              И таких 8 штук. Пишется на них через «прокладку» aufs примерно равномерно. Так что чтение тоже примерно равномерное.

              По гигабиту больше 950 Мб и не получится. Когда скорость отдачи подошла к 500 Мб (и кратковременные всплески до 800) поставили сетевую на 10 Гб. На тестах (1 URL * 200 потоков) получал 2,8 Гб выхлоп. Но это 1 URL, вероятно читает из кеша.
              Сколько получится в реальности буду посмотреть.
              await дисков мониторится, больше 10 мс не бывает.


            1. xXxSPYxXx
              30.11.2017 08:58

              У меня 20Г интерфейс, 8 SSD Дисков в RAID0, но мы упираемся в IOPS. Я думаю упремся при 15Г. Какие параметры ядра стоит подкрутить? Или лучше использовать каждый диск отдельно? Сохранность данных не важна.


              1. Yagoda123 Автор
                30.11.2017 09:46

                Как-то тут на хабрахабре проскакивала статья про RAID из SSD. Человек делал эксперименты, замерял результаты. Получилась плохая латентность. Как раз IOPS сильно падал.
                Я сознательно не стал собирать RAID, хотя железо вполне позволяет.
                Данные не настолько критичные, но потерять все равно жалко. Ну и было у меня несколько случаев, когда из-за смерти диска/контроллера терялся весь массив.
                Может вам лучше иметь каждый диск отдельно, писать на него через mhddfs/aufs, а при считывании находить где находится на самом деле и читать от-туда? Можно nginx(ом) это разрулить.


                1. xXxSPYxXx
                  30.11.2017 09:56

                  Не разу не использовал эти ФС. А если использовать LVM? И как я узнаю где лежит файл? =)
                  То есть RAID0 не спасает от высокой утилизации?


                  1. Yagoda123 Автор
                    30.11.2017 11:31

                    mhddfs/aufs, в принципе неплохая штука если надо логически объединить несколько каталогов/дисков. Из плюсов — каждый файл целиком лежит в одном месте. И при гибели одного диска остальное все целое.
                    Вот только большой трафик через эту прослойку гонять накладно для CPU.
                    Обычно на раздаче файлов много чтений и мало записей, поэтому писать в общую точку с автоматическим распределением, а читать из настоящего месторасположения — самое то.
                    LVM это тот-же RAID0, только чуть по-другому. Все равно «прослойка». И все плюсы с минусами от этого. LVM несколько удобнее — позволяет легко расширять дисковое пространство.
                    У обоих невозможно определить, где находится файл «по-настоящему», у обоих при смерти одного диска помирает все. И в обоих случаях, когда диск занят чем-то своим (это особенно касается SSD), доступ ко всему массиву в ступоре.
                    RAID0 (особенно аппаратный) хорошо помогает поднять IOPS если используются HDD. С SSD все на много печальнее. SSD периодически сами наводят у себя «порядок». Ну и весь массив будет недоступен пока какой-то диск занят своими делами.

                    О! Нашел статью: habrahabr.ru/company/webzilla/blog/227927


              1. Jokerjar
                30.11.2017 11:23

                Иногда единственное решение — расширение на несколько серверов. Учитывая, что аренда серверов с очень широким каналом стоит немалых денег, иногда даже выходит экономичнее взять несколько серверов и «запараллелить» их. У меня балансировка происходит прямо в логике сайта — скрипт по определенным правилам отдает контент клиенту с определенного сервера. Каждый сервер содержит одинаковые данные (зеркализируются rsync'ом). Заодно и резервная копия имеется.


  1. prodex
    28.11.2017 10:12

    Nginx отлично справляется с раздачей видео из коробки.

    1) >1 — отдается всегда целиком (файл полностью);

    Посмотрите в сторону раздачи mp4 через псевдо-стриминг (`ngx_http_mp4_module`).

    ```nginx
    location ~ \.1080\.mp4$ {
    mp4;
    mp4_buffer_size 20m;
    mp4_max_buffer_size 40m;
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    expires max;
    directio 10m;
    limit_rate 1024m;
    limit_rate_after 10m;
    }
    ```
    Получаем все плюшки nginx, в том числе range-bytes и контроль скорости отдачи.

    Полный пример: habrahabr.ru/post/265897/?#2-razdayuschiy-server

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

    Посмотрите в сторону hls. В плейлист `playlist.m3u8` добавляете любые фрагменты, а потом nginx отдает все как маленькие статические файлы.

    Пример `playlist.m3u8`:
    ```
    #EXTM3U
    #EXT-X-TARGETDURATION:13
    #EXT-X-ALLOW-CACHE:YES
    #EXT-X-PLAYLIST-TYPE:VOD
    #EXT-X-VERSION:3
    #EXT-X-MEDIA-SEQUENCE:1
    #EXTINF:10.000,
    cdn.example.com/video/1/part1.ts
    #EXTINF:10.000,
    cdn.example.com/video/1/part2.ts
    #EXTINF:10.000,
    cdn.example.com/video/1/part-100.ts
    #EXT-X-ENDLIST
    ```


    1. Yagoda123 Автор
      28.11.2017 10:23

      1. А ngx_http_mp4_module поймет mkv? А avi? А прочие?
      И уже уточнял. Проигрывается это (в том числе) на старых железяках типа ТВ-приставки. Так что надеяться на запросы с "?start=***" не приходится. Во всяком случае, в логах таких запросов нет. И далеко не факт, что эти железяки поймут playlist.m3u8.
      2. hls позволит склеить [первый файл с 123457 байта до конца]+[второй файл до 7777 байта]?


      1. Shtucer
        28.11.2017 10:34

        А ffmpeg не решит всех ваших хотелок?


        1. Yagoda123 Автор
          28.11.2017 10:36

          Конвертировать все? ООООООО Только не это.
          Или раздавать через ffmpeg?


          1. Shtucer
            28.11.2017 10:39

            Стримить через ffmpeg.


            1. Yagoda123 Автор
              28.11.2017 10:53

              Мне надо отдать по запросу HTTP. Поставить на паузу, перемотать вперед-назад, продолжить.


              1. prodex
                28.11.2017 11:03

                А какие были исходные требования?


                • в каких форматах файлы исходники;
                • в каких форматах нужно отдавать;
                • какая пропускная способность канала.


                1. Yagoda123 Автор
                  28.11.2017 11:09

                  Формат всякий-разный. Воспроизводит специализированный софт или железо. Изменить его не могу.
                  Отдавать как есть.
                  Канал 10 Gb.


              1. Shtucer
                28.11.2017 11:04

                http://ffmpeg.org/ffserver-all.html#http
                Ставьте, мотайте, продолжайте.



  1. prodex
    28.11.2017 10:42

    1. А ngx_http_mp4_module поймет mkv? А avi? А прочие?

    Только mp4.


    Так что надеяться на запросы с "?start=***"

    Можно сделать подмену или редирект. А если пришли заголовки range-bytes, то сразу редиректить. ngx_http_mp4_module умеет отрабатывать range-bytes.


    hls позволит склеить [первый файл с 123457 байта до конца]+[второй файл до 7777 байта]

    Позволяет. В playlist.m3u8 вставляете произвольные куски фрагментов любых видео какpart.ts (MPEG-TS).


    #EXTM3U
    #EXT-X-TARGETDURATION:13
    #EXT-X-ALLOW-CACHE:YES
    #EXT-X-PLAYLIST-TYPE:VOD
    #EXT-X-VERSION:3
    #EXT-X-MEDIA-SEQUENCE:1
    #EXTINF:10.000,
    http://cdn.example.com/video/1/v1-1.ts
    #EXTINF:10.000,
    http://cdn.example.com/video/1/v1-2.ts
    #EXTINF:10.000,
    http://cdn.example.com/video/1/v2-1.ts
    #EXTINF:10.000,
    http://cdn.example.com/video/1/v2-2.ts
    #EXTINF:10.000,
    http://cdn.example.com/video/1/v2-3.ts
    #EXT-X-ENDLIST


  1. Yagoda123 Автор
    28.11.2017 11:04

    Модули nginx http_flv_module и http_mp4_module не подходят.
    Сейчас имеется много контента, который этими модулями не поддерживаются.

    hls тоже не в тему — клиенты не поддерживают. И изменить что-то у клиента невозможно.

    Проблема у меня возникла именно с блокирующим чтением диска при обработке range-bytes запросов. Если кто предложит способ обойти именно эту проблему — подсказывайте плиз.


  1. rebel_web
    28.11.2017 11:13

    А вместо вот этого
    while(!feof($fp)){ ... echo fread($fp, $buf) ... }


    Можно попробовать "yield". Память есть в разы меньше
    Что то типа такого
    function getLines($file) { $f = fopen($file, 'r'); if (!$f) throw new Exception(); while ($line = fgets($f)) { yield $line; } fclose($f); }


    1. Yagoda123 Автор
      28.11.2017 11:19

      Это из питона? В принципе можно и на нем делать. Но тут принципиальной разницы нет. Все равно "$line = fgets($f)" сожрет кучу памяти. Если делать на рабочей машине — нормально. А на сервере, да на каждый запрос…
      Нафиг-нафиг, ночью бежать до серверной чтобы ребутнуть сервер.


      1. rebel_web
        28.11.2017 12:43

        нет это php. Вот тут человек пробовал разные варианты чтения.


        1. Yagoda123 Автор
          28.11.2017 12:58

          Там читался текстовый файл. У меня бинарные.
          Больше подошел бы stream_copy_to_stream, но в нем сложно копировать произвольную часть файла.
          И этот stream_copy_to_stream в указанной статье дал примерно тот-же memory_get_peak_usage что и получилось у меня с passthru('/bin/dd ....').
          Но у меня еще в скрипте дополнительно куча логики, которая ест память.
          Так что не то. Функционала мало.


  1. kenbo
    29.11.2017 02:25

    Для вывода в PHP можно писать напрямую в stdout: fwrite(STDOUT, fread($fd, $buf_size));


    1. Yagoda123 Автор
      29.11.2017 02:44

      Это тоже самое что и «echo fread()». Читаются данные в свой некоторый буфер php. Затем эти данные выплевываются в STDOUT. PHP, прежде чем прочитать данные, запросит у системы память в объеме указанного буфера. Соответственно, скрипт будет потреблять приватной памяти не меньше этого буфера. Если читаем по 100К, то вроде не страшно. Но скорость отдачи получается низкая. В ходе экспериментов выяснил, что делать буфер меньше 2М нельзя. Т.е. скрипт будет съедать памяти 2М под буфер + ~0,5М для своих нужд. Итого, 1000 клиентов съедят 2,5 GB.
      В моем же варианте «fpassthru($fd)» или «passthru('/bin/dd ...')» на 1000 клиентов придется максимум 1 GB.
      Затраты на сам /bin/dd + «прокладка» sh — это мелочи. Приватной памяти они потребляют мизер (меньше 2К вместе). Затраты памяти на сам исполняемый код можно не учитывать — они в памяти в одном экземпляре.
      Затраты времени на вызов системной команды маленькие — все нужное уже в памяти.


  1. Jokerjar
    29.11.2017 02:52

    А какие настройки пула php-fpm? Подозреваю, что с такой схемой child-потоков плодится немереное количество?


    1. Yagoda123 Автор
      29.11.2017 03:05

      pm = dynamic
      pm.max_children = 500 // пока хватает. расчеты и мониторинг показывают, что можно довести до 2000
      pm.start_servers = 20 // т.е. 20 штук всегда будут ожидать подключение. обеспечивается скорость ответа
      pm.min_spare_servers = 20
      pm.max_spare_servers = 100
      pm.max_requests = 50 // боялся утечек памяти, вроде нормально.

      Памяти на борту 20 G. Вот сейчас (в nginx еще остались настройки на собственную отдачу, пока не убираю, жду выходных). 100 соединений, 300 Мб выхлоп:
      # free -m
      total used free shared buffers cached
      Mem: 20114 19382 732 410 374 16318
      -/+ buffers/cache: 2689 17425
      Swap: 16363 0 16363

      ..../status:
      pool: www
      process manager: dynamic
      start time: 27/Nov/2017:11:39:48 +1000
      start since: 167057
      accepted conn: 92179
      listen queue: 0
      max listen queue: 0
      listen queue len: 0
      idle processes: 39
      active processes: 65
      total processes: 104
      max active processes: 151
      max children reached: 0
      slow requests: 0


      1. Yagoda123 Автор
        29.11.2017 03:54

        Добавлю.
        В php.ini для php-fpm отключены все дополнительные модули (mysql, mysqli и прочее).
        Т.е. практически «голый» php. По-идее, можно было бы вообще пересобрать php5-fpm без cripto, ssl, xml и прочего, в данном случае не нужного. В результате потребление памяти сократилось бы еще. Но и так, 1 МБ на процесс, меня вполне устраивает.


  1. lexore
    30.11.2017 11:55

    Способ занимательный, но боюсь вы просто не настроили nginx должным образом.
    250 Mbit/s считается маленькой нагрузкой, если говорить про отдачу видео с диска.
    Все интересное начинается от 1 Gbit/s, а на таких скоростях php может стать узким горлышком.


    1. Yagoda123 Автор
      30.11.2017 12:06

      Вероятно Вы правы.
      Очень возможно, что у меня просто не хватало worker_rlimit_nofile в nginx.
      Но почему тогда fastcgi_pass помог? Ведь это тоже «файл» для воркера.

      php не принципиально. Наверное, лучше было бы сделать на питоне.
      Но даже в том виде что сейчас (раздача nginx — fastcgi — php) все свистит и не грузит систему. Мне самое главное, что получился очень быстрый коннект. Т.е. отдача на запрос начинается очень быстро. По большому счету, на соединении большой скорости и не нужно. Но этих соединений много.


      1. lexore
        30.11.2017 12:44
        +1

        В вашем конфиге я не увидел "aio on;", а его надо явно включать (по умолчанию он выключен).
        Просто может быть ситуация, когда вы его вкомпилили, но не включили.
        Поэтому я бы рекомендовал на досуге попробовать настройки из коммента выше, только добавить явно "aio on;" (что-то он там тоже не указан).


        1. Yagoda123 Автор
          30.11.2017 12:52

          Полностью конфиг не приводил. Указал только location.

          А вообще, да. И aio и thread_pool, и много чего еще пробовал ))
          Числа были несколько другие только.

          Все-таки, думаю, было мало worker_rlimit_nofile.
          Было 4К то-ли из коробки, то-ли сам делал, не помню уже. Хотя, 4к на воркер должно бы хватать чисто умозрительно. Но вот сейчас сделал 64к и на запросы сервер стал отвечать стабильнее.


          1. xXxSPYxXx
            30.11.2017 12:57

            user www-data;
            worker_processes 32;
            worker_rlimit_nofile 65535;

            pid /var/run/nginx.pid;
            events {
            worker_connections 10240;
            multi_accept on;
            use epoll;
            }

            # file handle caching / aio
            open_file_cache max=100000 inactive=5m;
            open_file_cache_valid 5m;
            open_file_cache_min_uses 2;
            open_file_cache_errors on;
            aio threads;


            1. zuborg
              30.11.2017 15:18

              Попробуйте добавить
              sendfile on;


              1. xXxSPYxXx
                30.11.2017 16:27

                У меня он прописан.