Badoo відкриває вихідні коди Live Streaming Daemon


Для того щоб ми могли вважати статистику, наш сайт в своїй роботі генерує величезну кількість подій. Наприклад, при відправці повідомлення іншому користувачеві, при заході користувача на сайт, при зміні місця розташування і т. д. Події являють собою рядок у форматі JSON або ГПБ (Google Protocol Buffers) і містять час відправлення, ідентифікатор користувача, тип події, а також поля, що відносяться безпосередньо до самої події (наприклад, координати користувача).

Кожну секунду генеруються сотні тисяч подій, і нам потрібні інструменти, щоб їх збирати і аналізувати ефективно та з мінімальною затримкою. Ми розглядали декілька існуючих рішень для цієї задачі і донедавна використовували демон під назвою Scribe від Facebook. Він в цілому нас влаштовував і дозволяв робити все, що нам потрібно. Проте в якийсь момент Facebook закинув свою розробку, і при деяких умовах Scribe почав у нас падати (наприклад, при перевантаженні upstream-серверів). Самостійно усунути причину падінь демона у нас не вийшло, тому ми почали шукати альтернативу.


Наші вимоги до системи доставки подій були наступні:
  • наявність локального (проксі) демона;
  • збереження подій на диск у разі недоступності приймаючого сервера;
  • можливість маршрутизації подій за категоріями;
  • шардирование потоків даних по хешу (від user_id або іншого) і round-robin;
  • запис подій у файли на приймаючій стороні (scribe-like);
  • нормальна робота в умовах високої latency мережі (доставка подій між ДЦ);
  • масштабованість прийому та відсилання до мільйона подій в секунду;
  • легкість експлуатації, адекватне споживання ресурсів.


Ми розглядали наступні варіанти:
  • Apache Flume: нестабільний, втрачає події при падінні, якщо не використовувати Spooling Directory Source, який має дуже незручний API;
  • FluentD: занадто низька продуктивність, в іншому дуже гарний;
  • Apache Kafka: немає локального агента (див. issues.apache.org/jira/browse/KAFKA-1955).


На жаль, жоден з цих варіантів не вирішує всі наші проблеми, тому ми вирішили написати свою систему і назвали її Live Streaming Daemon (LSD).

Що вмів Scribe?
Щоб зрозуміти, що робить LSD і навіщо він потрібен, давайте спочатку розглянемо докладніше фічі, які ми використовували в scribe.

Наявність локального демона

Scribe працює по архітектурі «клієнт-сервер», де клієнтами називаються машини, які генерують події, а серверами — машини, які їх отримують. Щоб економити ресурси і уміти буферизувати на диск події в разі проблем з доставкою, Scribe пропонувати запускати инстансы клієнта на кожній машині, на якій генеруються події. Додаток, генеруючий події, з'єднується з локальним клієнтом через unix або tcp socket і посилає до нього через протокол Apache Thrift. Передбачається, що локальний проксі буде завжди доступний і буде відповідати за невеликий час.

В цілому в більшості випадків все так і працює, проте іноді локальний інстанси Scribe може почати відповідати довше звичайного або взагалі аварійно завершитися. Тому у нас був механізм, який зберігає подія в локальні файли замість Scribe, якщо він недоступний. Окремим cron-скриптом ми потім відправляли ці події в Scribe, коли він піднімався.

Можливість маршрутизації подій за категоріями

Категорією події називається, по суті, ім'я директорії, в яку буде записано те або інша подія на приймаючому сервері. Різні типи подій має сенс класти в різні категорії, оскільки обробник для них може відрізнятися. У Scribe передбачена можливість посилати різні категорії на різні сервера і запитує вона маскою імені категорії, наприклад
debug_*
.

В нашій конфігурації всі події за замовчуванням доставляються в європейський ДЦ. Якщо потрібно доставити подія в межах ДЦ, ми надсилаємо подія, яка має префікс
local_
, або ж, якщо ми хочемо доставити подія в певний ДЦ, ми додаємо префікс з ім'ям цього ДЦ. У конфігурації демона прописані різні маршрути для цих категорій, і вони доставляються туди, куди потрібно. При доставці у віддалені ДЦ можуть використовуватися проміжні вузли для буферизації подій.

Шардирование потоків даних

Іноді буває зручно доставляти дані, які відносяться до конкретного користувача, на один і той же сервер. У деяких випадках це дозволяє значно поліпшити продуктивність обробки за рахунок кешування даних користувача на невеликий час.

Як правило, дані розподіляються просто за алгоритмом round-robin, тобто кожен наступний шматок даних надсилається на наступний сервер зі списку, і так по колу. У Scribe є недолік при роботі в обох режимах: демон «запам'ятовує» сервер, на який потрібно доставити конкретну подію, і при недоступності одного з приймаючих серверів події будуть збиратися на диску і нікуди не доставлятися, навіть якщо інші сервера доступні і здатні прийняти і обробити весь потік подій.

Запис подій у файли на приймаючій стороні

На приймаючій стороні (тобто на стороні сервера) всі події записуються у файли виду
<назва_категорії>/<назва_категорії><дата>_<лічильник>
, а також створюється симлинк виду
<назва_категорії>/<назва_категорії>_current
на останній файл в категорії. Файли ротуються на підставі минулого часу (наприклад, 60 секунд) або об'єму (наприклад, 10 Мб) в залежності від того, що станеться раніше.

Якщо категорія називається, наприклад, error_log, то ієрархія файлів і директорій буде наступна:

/var/scribe/error_log/
|-- error_log-2016-09-13_004742
|-- error_log-2016-09-13_004743
|-- error_log-2016-09-13_004744
`-- error_log_current -> error_log-2016-09-13_004744



Запис здійснюється завжди в останній файл. У попередні файли сервер не пише, їх можна вільно читати і видаляти після того, як файл повністю оброблений.

Нормальна робота в умовах високої latency мережі

Клієнт Scribe відправляє дані невеликими пачками і чекає підтвердження з віддаленої сторони перед тим, як відправити нову пачку. Це дуже погано працює, наприклад, у разі пересилання подій через Атлантичний океан, де затримка передачі даних складає приблизно 125 мс. Якщо максимальний розмір пачки, приміром, становить 0,1 Мб, то за одну секунду таким способом можна передати лише 0,1 Мб / 0,125 = 0,8 Мб/с. Це обмеження можна обійти, якщо не чекати підтвердження для кожної пачки, а відправляти події в потоковому режимі.

Що пропонує LSD?
В цілому основних претензій до Scribe у нас було всього дві:
  1. Нестабільність і втрата даних при падінні демона.
  2. При падінні приймаючого сервера трафік не перерозподіляється між рештою серверами автоматично, потрібно ручне втручання.
LSD вирішує ці дві проблеми і задовольняє нашим вимогам по доставці подій, про які ми говорили на початку.

Захист від втрати даних при падінні демона

Не буває софта без помилок, тому замість того, щоб постаратися зробити LSD «неубиваемым» і завжди відповідає за адекватне час, було вирішено піти іншим шляхом: клієнти будуть завжди писати події у файли, а LSD-клієнт буде ці файли читати і доставляти на потрібні машини. Цей спосіб зручний ще і тим, що не потребує драйверів Thrift, Protocol Buffers і т. д., події можна відправляти хоч з shell-скрипта.

Щоб відправити подію, потрібно записати рядок з цією подією в кінець файлу виду <category>/<filename>.log, де <category> — ім'я категорії події. Як <filename> може служити будь-яка монотонно зростаюча рядок, заснована на поточну дату і час. Такий формат був обраний не випадково і дозволяє пересилати на інші сервера події, доставлені з допомогою LSD або Scribe. Як <filename> ми рекомендуємо використовувати дату і час у форматі YYYYMMDDHHII (наприклад, 201609131714). При виборі такого формату файли створюються максимум раз в хвилину та їх імена монотонно зростають.

Якщо потрібно відправляти події розміром більше 4 Кб ( stackoverflow.com/questions/1154446/is-file-append-atomic-in-unix ) з декількох процесів, то потрібно брати файлову блокування перед записом події у файл, щоб рядки не перемішувалися. Можна додавати суфікс _big до імені файлу і писати великі події в окремий файл, щоб не брати блокування для маленьких подій.

Також підтримується plain-формат виду <category>.log, і в такому випадку створення піддиректорії не потрібно. Такий формат зручно використовувати при відсиланні подій з shell-скриптів і для збору логів.

Автоматичне перерозподіл потоку подій

Якщо падає один з серверів, на які доставляються події, то вони автоматично перерозподіляються за рештою серверів. Якщо ж один із серверів працює повільніше інших, то в цьому випадку йому просто дістанеться такий потік подій, який він в змозі прийняти.

Це також означає, що одноразова доставка не гарантується, оскільки недоступність сервера визначається на підставі таймауту. Можлива ситуація, коли події можуть успішно надходити на сервер, але підтвердження про це приходити не будуть, або ж будуть приходити з великим запізненням. У такому разі LSD-клієнт заново пошле пачку подій, підтвердження якої не настав за таймаут (за замовчуванням 30 секунд).

Доставка подій в режимі реального часу

Оскільки ми вибрали ім'я Live Streaming Daemon, потрібно відповідати :). Коли вистачає пропускної здатності мережі та продуктивності сервера на приймаючій стороні, доставка подій здійснюється в режимі реального часу — ніяких штучних затримок при доставці не вноситься. Це зручно, якщо ви доставляєте логи або створюєте багато проміжних вузлів для пересилання подій. З іншого боку, доставка в режимі реального часу вимагає більшої кількості ресурсів, ніж якщо б події накопичувалися і вирушали раз на кілька секунд (з такими налаштуваннями ми використовували Scribe). Тому споживання CPU у LSD в середньому трохи вище, ніж у Scribe, проте різниця не дуже значна.

Продуктивність

На жаль, ми не змогли виміряти продуктивність Scribe на нашому потоці подій для внутрішньої системи аналітики UDS, оскільки scribe-клієнти падали під навантаженням (про UDS не так давно розповідав Олександр Крашенніков www.percona.com/blog/2016/08/29/percona-live-europe-featured-talk-with-alexander-krasheninnikov-processing-11-billion-events-a-day-with-spark-in-badoo ).

Один LSD-сервер легко справляється з потоком подій в 2 гігабіта/з (400k подій/с), що надходять c тисяч серверів. Відповідно, щоб прийняти потік в 1 мільйон подій в секунду, потрібно всього 3 сервера, при цьому кожен з серверів повинен бути оснащений двома гігабітними мережевими картами.

Open-source

Вихідні коди LSD знаходяться на GitHub: github.com/badoo/lsd (для встановлення наберіть команду go get github.com/badoo/lsd).
Демон працює під Linux і macOS, але для промислового використання рекомендується використовувати Linux.

Крім LSD, у нас є велика кількість інших проектів, викладених в open source, подивитися і вивчити які ви можете в нашому техблоге: tech.badoo.com/ru/open-source

Юрій Насретдинов, старший розробник, Badoo
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.