Как стать автором
Обновить

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

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

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.
Хм… Надо именно такие параметры попробовать.
Похожее (но не совсем так) делал, ничего хорошего не получил. Только памяти nginx съел много.
В документации: «Невыравненный конец файла будет читаться блокированно. То же относится к запросам с указанием диапазона запрашиваемых байт (byte-range requests)». По моим оценкам так и получается.
Добавлю. Весь контент у меня находится на 8ми SAS-3 12Gbit/s дисках.
Читать все через «прокладку» mhddfs/aufs, по моим оценкам, «не гуд». Во всяком случае, тесты показали скорость чтения на много меньше чем читать напрямую.
Поэтому все запросы rewrit(ом) приводил к нужному мне виду и переключался на нужный диск при помощи try_files. До некоторых пор это помогало. Но трафик подрос, клиентов онлайн стало больше и появились «тормоза». Вот и начал экспериментировать.
У меня на серверах с подобной задачей все упирается в скорость чтения с дисков или производительность канала связи. В остальном, Nginx отлично справляется с настроенным aio, как я описывал выше, и тюнингом sysctl (тут уже индивидуально нужно тестировать). Если и использовать скрипты в схеме с отдачей видео, то только с x-accel-redirect, как я считаю. На счет второго пункта, можно подготавливать склеенный файл (на ssd или tmpfs) и x-accel-redirect'ить на него (продумав при этом периодическую чистку).
У меня в диски и канал пока не упирается. Канал 10 Gb.
По факту в выходные было 850 Mb. На используемых дисках await выше 10 ms не поднимается. Обычно 2 — 5.
Склеивать у меня не выход. Надо уметь отдавать произвольную часть файла. проблема именно в этом. И в том, что практически все запросы такие.
А можно пример настроек sysctl?
Какие именно интересуют?
Все постить много…
Ну хотя бы основные, что стоит поменять после запуска сервера с HDD дисками и 1 гбит каналом.
#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
Спасибо, посмотрю что и за что отвечает

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


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

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

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


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

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

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

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


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

Сколько траффика отдаете?
Интерфейс 10Gb. Зафиксированный максимум 850 Mb. На тестах (1 URL * 200 потоков) выдал 2,8 Gb.
Вопрос был Jokerjar.
Вам я бы посоветовал привести видео к одному формату и стримить через DASH, HDS, HLS, MSS. Конечно нужно смотреть что клиенты поддерживают. Ну и железо слабовато для утилизации 10G канала.
Приводить к одному формату, к сожалению, невозможно. Там реально зоопарк.

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

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

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

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

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

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

Т.е. связка nginx (без буферизации) — fastcgi оказалась эффективной для такой задачи.
Ну и решил поделиться своим удивлением ))
Сервера по гигабиту, отдают в пик примерно 937 мегабит, упирается в скорость чтения с дисков (обычные HDD 4Tb).
Ну, у меня не совсем обычные HDD.
SAS-3, 12Gb/s, контроллер соответствующий.
И таких 8 штук. Пишется на них через «прокладку» aufs примерно равномерно. Так что чтение тоже примерно равномерное.

По гигабиту больше 950 Мб и не получится. Когда скорость отдачи подошла к 500 Мб (и кратковременные всплески до 800) поставили сетевую на 10 Гб. На тестах (1 URL * 200 потоков) получал 2,8 Гб выхлоп. Но это 1 URL, вероятно читает из кеша.
Сколько получится в реальности буду посмотреть.
await дисков мониторится, больше 10 мс не бывает.
У меня 20Г интерфейс, 8 SSD Дисков в RAID0, но мы упираемся в IOPS. Я думаю упремся при 15Г. Какие параметры ядра стоит подкрутить? Или лучше использовать каждый диск отдельно? Сохранность данных не важна.
Как-то тут на хабрахабре проскакивала статья про RAID из SSD. Человек делал эксперименты, замерял результаты. Получилась плохая латентность. Как раз IOPS сильно падал.
Я сознательно не стал собирать RAID, хотя железо вполне позволяет.
Данные не настолько критичные, но потерять все равно жалко. Ну и было у меня несколько случаев, когда из-за смерти диска/контроллера терялся весь массив.
Может вам лучше иметь каждый диск отдельно, писать на него через mhddfs/aufs, а при считывании находить где находится на самом деле и читать от-туда? Можно nginx(ом) это разрулить.
Не разу не использовал эти ФС. А если использовать LVM? И как я узнаю где лежит файл? =)
То есть RAID0 не спасает от высокой утилизации?
mhddfs/aufs, в принципе неплохая штука если надо логически объединить несколько каталогов/дисков. Из плюсов — каждый файл целиком лежит в одном месте. И при гибели одного диска остальное все целое.
Вот только большой трафик через эту прослойку гонять накладно для CPU.
Обычно на раздаче файлов много чтений и мало записей, поэтому писать в общую точку с автоматическим распределением, а читать из настоящего месторасположения — самое то.
LVM это тот-же RAID0, только чуть по-другому. Все равно «прослойка». И все плюсы с минусами от этого. LVM несколько удобнее — позволяет легко расширять дисковое пространство.
У обоих невозможно определить, где находится файл «по-настоящему», у обоих при смерти одного диска помирает все. И в обоих случаях, когда диск занят чем-то своим (это особенно касается SSD), доступ ко всему массиву в ступоре.
RAID0 (особенно аппаратный) хорошо помогает поднять IOPS если используются HDD. С SSD все на много печальнее. SSD периодически сами наводят у себя «порядок». Ну и весь массив будет недоступен пока какой-то диск занят своими делами.

О! Нашел статью: habrahabr.ru/company/webzilla/blog/227927
Иногда единственное решение — расширение на несколько серверов. Учитывая, что аренда серверов с очень широким каналом стоит немалых денег, иногда даже выходит экономичнее взять несколько серверов и «запараллелить» их. У меня балансировка происходит прямо в логике сайта — скрипт по определенным правилам отдает контент клиенту с определенного сервера. Каждый сервер содержит одинаковые данные (зеркализируются rsync'ом). Заодно и резервная копия имеется.
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. А ngx_http_mp4_module поймет mkv? А avi? А прочие?
И уже уточнял. Проигрывается это (в том числе) на старых железяках типа ТВ-приставки. Так что надеяться на запросы с "?start=***" не приходится. Во всяком случае, в логах таких запросов нет. И далеко не факт, что эти железяки поймут playlist.m3u8.
2. hls позволит склеить [первый файл с 123457 байта до конца]+[второй файл до 7777 байта]?

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

Конвертировать все? ООООООО Только не это.
Или раздавать через ffmpeg?
Мне надо отдать по запросу HTTP. Поставить на паузу, перемотать вперед-назад, продолжить.

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


  • в каких форматах файлы исходники;
  • в каких форматах нужно отдавать;
  • какая пропускная способность канала.
Формат всякий-разный. Воспроизводит специализированный софт или железо. Изменить его не могу.
Отдавать как есть.
Канал 10 Gb.
  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
Модули nginx http_flv_module и http_mp4_module не подходят.
Сейчас имеется много контента, который этими модулями не поддерживаются.

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

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

А вместо вот этого
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); }

Это из питона? В принципе можно и на нем делать. Но тут принципиальной разницы нет. Все равно "$line = fgets($f)" сожрет кучу памяти. Если делать на рабочей машине — нормально. А на сервере, да на каждый запрос…
Нафиг-нафиг, ночью бежать до серверной чтобы ребутнуть сервер.
нет это php. Вот тут человек пробовал разные варианты чтения.
Там читался текстовый файл. У меня бинарные.
Больше подошел бы stream_copy_to_stream, но в нем сложно копировать произвольную часть файла.
И этот stream_copy_to_stream в указанной статье дал примерно тот-же memory_get_peak_usage что и получилось у меня с passthru('/bin/dd ....').
Но у меня еще в скрипте дополнительно куча логики, которая ест память.
Так что не то. Функционала мало.
Для вывода в PHP можно писать напрямую в stdout: fwrite(STDOUT, fread($fd, $buf_size));
Это тоже самое что и «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К вместе). Затраты памяти на сам исполняемый код можно не учитывать — они в памяти в одном экземпляре.
Затраты времени на вызов системной команды маленькие — все нужное уже в памяти.
А какие настройки пула php-fpm? Подозреваю, что с такой схемой child-потоков плодится немереное количество?
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

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

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

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

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

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

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

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

Все-таки, думаю, было мало worker_rlimit_nofile.
Было 4К то-ли из коробки, то-ли сам делал, не помню уже. Хотя, 4к на воркер должно бы хватать чисто умозрительно. Но вот сейчас сделал 64к и на запросы сервер стал отвечать стабильнее.
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;
Попробуйте добавить
sendfile on;
У меня он прописан.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации