Розробляємо систему моніторингу на 55000 відео-потоків RTP

Що моніторимо?
Підключення до ethernet-лінків можливе тільки в режимі моніторингу, за допомогою оптичних відгалужувачів, тобто в неінтрузивному режимі. Таке підключення краще, оскільки в цьому випадку трафік не проходить крізь обладнання і, отже, воно ніяк не може вплинути на якість послуг (падіння рівня оптичного сигналу на спліттері вважаємо зовсім незначним). Для операторів це дуже важливий аргумент. А для розробників з такого підключення слід важливий нюанс — за потоками завжди доведеться спостерігати збоку, т.к. передавати в мережу пакети не можна (наприклад, не можна надіслати пінг та отримати відповідь). Значить, працювати доведеться в умовах нестачі інформації.
А що вимірюємо?
RTP може “йти” поверх UDP, і (переважно, у 90% випадків) поверх RTSP/TCP як “Interleaving data”. Так-так, незважаючи на те, що в RFC з RTSP сказано, що режим Interleaving Data краще не використовувати - див. 10.12, rfc2326).
І що тут складного?
Шукаємо відповідне рішення ...
Ми, природно, прагнули максимально використати власний досвід. До моменту прийняття рішення у нас вже була реалізація обробки ethernet-пакетів на FPGA-powered девайсі Беркут-МХ (простіше - MX). За допомогою Беркут-MX ми вміли отримувати із заголовків Ethernet-пакетів потрібні поля для аналізу. Досвіду обробки такого обсягу трафіку засобами "звичайних" серверів у нас не було, на жаль, тому на подібне рішення дивилися з деякою побоюванням.
Що можна виміряти за полями RTP-пакету?
Формат пакету RTP описаний у rfc3550.

Цілком очевидно, що sequence number дозволяє визначити такі параметри потоку:
- втрати пакетів (frame loss);
- повторне надсилання пакета (duplicate);
- зміна порядку приходу (reordering);
- перезавантаження камери, при великому розриві в послідовності.
Timestamp дозволяє виміряти:
- варіацію затримки (ще називають джиттером). При цьому на приймальній стороні має працювати 90 КГц лічильник;
- в принципі, затримка проходження пакета. Але для цього потрібно синхронізувати час камери з timestamp'ом, а це можливо якщо камера передає sender reports (RTCP SR), що в загальному випадку неправильно, т.к. у реальному житті багато камер ігнорують посилку RTCP SR (приблизно половина камер, з якими нам довелося попрацювати).
Детальні алгоритми вимірювання параметрів виходять за рамки статті, не заглиблюватимуся. Якщо цікаво, то rfc3550 є приклад коду обчислення втрат і формули для обчислення джиттера. Головний же висновок полягає в тому, що для вимірювань базових характеристик транспортного потоку достатньо лише кількох полів з RTP-пакетів та NAL-юнітів. А решта інформації у вимірах не бере участі і її можна і потрібно відкинути!

Як ідентифікувати потоки RTP?
Що цікаво, ми спочатку зробили ідентифікацію камер тільки по IP джерела та SSRC, покладаючись на те, що SSRC має бути випадковим, але на практиці виявилося, що багато камер встановлюють SSRC у фіксоване значення (скажімо, 256). Мабуть, це з економією ресурсів. У результаті нам довелося до ідентифікатора камери додати ще порти. Це вирішило проблему унікальності повністю.
Як відокремити RTP-пакети від решти трафіку?
Залишилося питання: як Беркут-MX, прийнявши пакет, зрозуміє, що це RTP? RTP-заголовок не має такої явної ідентифікації, як IP, він не має контрольної суми, передаватиметься вінможе за UDP з номерами портів, які вибираються динамічно під час встановлення з'єднання. А в нашому випадку більшість з'єднань вже давно встановлені і чекати на переустановку можна дуже довго.
Для вирішення цього завдання в rfc3550 (Appendix A.1) рекомендується перевіряти біти версії RTP – це два біти, і поле Payload Type (PT) – сім біт, яке у разі динамічного типу приймає невеликий діапазон. Ми на практиці з'ясували, що для того безлічі камер, з якими ми працюємо, PT укладається в діапазон від 96 до 100.
Є ще один фактор - парність порту, але як показала практика, не завжди дотримується, тому від нього довелося відмовитися.
Таким чином, поведінка Беркут-MX така:
- одержуємо пакет, розбираємо на поля;
- якщо версія дорівнює 2 і payload type перебуває у заданих межах, то відправляємо заголовки серверу.
Вочевидь, що з такому підході є хибнопозитивні спрацьовування, т.к. під такі звичайні критерії можуть потрапляти не тільки RTP-пакети. Але для нас важливо, що RTP-пакет ми точно не пропустимо, а неправильні пакети відфільтрує вже сервер.
Рухаємось далі…
У результаті сервер працює не з 50-60 Гігабіт/с, а максимум з 5% (саме така вийшла пропорція даних, що відсилаються до середнього розміру пакета). Тобто на вході всієї системи 55 Гігабіт/с, а на сервер потрапляє всього не більше 3 Гігабіт/с!
У результаті вийшла така архітектура:

І перший результат у такій конфігурації ми отримали за два тижні після постановки початкового ТЗ!
Чим у підсумку зайнятий сервер?
Отже, що робить сервер в нашій архітектурі? Його завдання:
- слухати UDP-сокет та вичитувати з нього поля з упакованими заголовками;
- розбирати прихожіпакети та діставати звідти поля RTP заголовків разом із ідентифікаторами камери;
- співвідносити отримані поля з тими, що були отримані раніше, і розуміти, чи загубилися пакети, чи посилалися пакети повторно, чи змінювався порядок приходу, якою була варіація затримки проходження пакета (джиттер) тощо;
- фіксувати виміряне в основі з прив'язкою до часу;
- аналізувати базу та генерувати звіти, посилати трапи про критичні події (високі втрати пакетів, зникнення пакетів від якоїсь камери тощо).
При тому, що сумарний трафік на вході сервера складає близько 3 Гігабіт/с, сервер справляється навіть за умови, що ми не використовуємо жодних DPDK, а працюємо просто через софт linux' (попередньо збільшивши розмір буфера для сокету, звичайно). Більше того, можна буде підключати нові лінки та MX'и, тому що запас продуктивності залишається.
Ось як виглядає top сервер (це top тільки одного lxc-контейнера, звіти генеруються в іншому):

З нього видно, що все навантаження по розрахунку параметрів якості та обліку статистики розподілено за чотирма процесами рівномірно. Нам вдалося досягти такого розподілу за рахунок застосування хешування в FPGA: за IP вважається хеш-функція, а молодші біти отриманого хешу визначають номер UDP-порту, на який піде статистика. Відповідно, кожен процес, який слухає свій порт, отримує приблизно однакову кількість трафіку.
Cons and pros
Настав час похвалитися та зізнатися у недоліках отриманого рішення.
Почну з плюсів:
- відсутність втрат на стику з 10G-лінками. Оскільки весь “удар” він бере ПЛИС, ми можемо не сумніватися у цьому, що буде проаналізовано кожен пакет;
- для моніторингу 55000 камер (ібільше) потрібно лише один сервер з однією 10G карткою. Ми поки що використовуємо сервери на базі 2х Xeon c 4ма ядрами по 2400 МГц кожне. Вистачає із запасом: паралельно зі збором інформації генеруються звіти;
- моніторинг 8-ми "десяток" (10G лінків) укладається всього в 2-3 юніти: не завжди під систему моніторингу є багато місця та харчування у стійці;
- при підключенні лінків від MX'ів через комутатор можна додавати нові лінки без зупинки моніторингу, т.к. ніякі плати в сервер вставляти не треба і для цього не потрібно вимикати його;
- сервер не перевантажений даними, він отримує лише те, що потрібно;
- заголовки з MX'а приходять у jumbo Ethernet-пакеті, отже процесор не захлинеться перериваннями (до того ж ми не забуваємо і про interrupt coalescing).
Зрештою, у нас вийшов програмно-апаратний комплекс, в якому ми можемо контролювати і ту частину, яка парсить пакети на інтерфейсах, і ту, що веде статистику. Повний контроль над усіма вузлами системи буквально врятував нас, коли камери почали перекладати на RTSP/TCP interleaved mode. Тому що в цьому випадку заголовок RTP перестав розташовуватися в пакеті фіксованого зміщення: він може знаходитися де завгодно, навіть на кордоні двох пакетів (перша половина в одному, друга - в іншому). Відповідно, алгоритм отримання RTP-заголовка та його полів зазнав кардинальних змін. Нам довелося зробити TCP reassembling на сервері для всіх 50000 з'єднань — звідси досить високе навантаження в top'і.
Ми ніколи до цього не працювали у сфері високонавантажених додатків, але нам вдалося вирішити завдання за рахунок наших скілів у FPGA і вийшло досить непогано. Навіть залишився запас - наприклад, до системи з 55000 камерами можна підключити ще 20-30 тисячпотоків.
Тюнінг linux'ових підсистем (розподіл черг за перериваннями, збільшення приймальних буферів, директивне виділення ядер на конкретні процеси тощо) я залишив поза рамками статті, т.к. ця тема і так вже дуже добре висвітлена.
Я описав далеко не все, граблів було зібрано чимало, тому не соромтеся ставити запитання :)
Дякую всім, хто дочитав до кінця!