Хабрахабр

Bioyino — распределённый, масштабируемый агрегатор метрик

Как и мы. Итак, вы собираете метрики. Конечно же, нужные для бизнеса. Мы тоже собираем метрики. Сегодня мы расскажем о самом первом звене системы нашего мониторинга — statsd-совместимом сервере агрегации bioyino, зачем мы его написали и почему отказались от brubeck.

Он написан на C. Из предыдущих наших статей (1, 2) можно узнать, что до некоторого времени метки мы собирали при помощи brubeck. метрик в секунду (MPS) в пике. С точки зрения кода — простой как пробка (это важно, когда вы хотите контрибьютить) и, самое главное, без особых проблем справляется с нашими объёмами в 2 млн. MPS со звёздочкой. Документация заявляет поддержку 4 млн. (Сколько MPS можно получить, если оставить сеть как есть, нам неизвестно). Это означает, что заявленную цифру вы получите, если правильно настроите сеть на Linux. Несмотря на эти преимущества, к brubeck у нас было несколько серьёзных претензий.

В последние несколько месяцев (где-то с февраля-марта 2018) активность возобновилась, но перед этим было почти 2 года полного затишья. Претензия 1. Github — разработчик проекта — перестал его поддерживать: публиковать патчи и фиксы, принимать наши и (не только наши) PR. Кроме того, проект разрабатывается для внутренних нужд Gihub, что может стать серьёзным препятствием для внедрения новых возможностей.

Brubeck собирает для агрегации всего 65536 значений. Претензия 2. Точность вычислений. В результате такого семплирования, значения максимумов и минимумов выглядят бесполезными. В нашем случае для некоторых метрик в период агрегации (30 сек) может приходить гораздо больше значений (1 527 392 в пике). Например, вот так:


Как было


Как должно было быть

Добавьте сюда баг с переполнением 32-битного float, которое вообще отправляет сервер в segfault при получении с виду невинной метрики, и становится вообще отлично. По той же причине суммы вообще считаются некорректно. Баг, кстати, так и не исправлен.

На момент написания статьи мы готовы предъявить её всем 14 более-менее работающим реализациям statsd, которые нам удалось найти. И, наконец, Претензия X. Или пусть ещё не выросла, но метрики для вас уже важны настолько, что даже короткие, 2-3 минутные провалы на графиках уже могут стать критичными и вызвать приступы непреодолимой депрессии у менеджеров. Давайте представим, что некоторая отдельно взятая инфраструктура выросла настолько, что принимать 4 млн MPS уже недостаточно. Так как лечение депрессии — дело неблагодарное, необходимы технические решения.

Во-вторых, масштабирование, чтобы получить возможность принимать больше 4 млн MPS, при этом не копать вглубь сетевого стека Linux и спокойно расти «вширь» до нужных размеров. Во-первых, отказоустойчивость, чтобы внезапная проблема на сервере не устроила в офисе психиатрический зомби апокалипсис.

«О! Поскольку по масштабированию запас у нас был, мы решили начать с отказоустойчивости. Это просто, это мы умеем», — подумали мы и запустили 2 сервера, подняв на каждом копию brubeck. Отказоустойчивость! Проблему отказоустойчивости мы этим решили, но… не очень хорошо. Для этого нам пришлось копировать трафик с метриками на оба сервера и даже написать для этого небольшую утилитку. Если вдруг один сервер откажет, у нас всегда есть второй с собственной копией агрегированных данных. Сначала всё было вроде бы здорово: каждый brubeck собирает свой вариант агрегации, пишет данные в Graphite раз в 30 секунд, перезаписывая старый интервал (это делается на стороне Graphite). Связано это с тем, что 30-секундные интервалы у brubeck не синхронизированы, и в момент падения один из них не перезаписывается. Но вот проблема: если сервер отказывает, на графиках возникает «пила». Вполне терпимо, но хочется лучше! В момент запуска второго сервера происходит то же самое. Все метрики по-прежнему «летят» на одиночный сервер, и поэтому мы ограничены теми же самыми 2-4 млн MPS в зависимости от прокачки сети. Проблема масштабируемости тоже никуда не делась.

То есть такой, в котором реализована синхронизация между нодами по времени и метрикам. Если немного подумать о проблеме и одновременно покопать лопатой снег, то в голову может прийти такая очевидная идея: нужен statsd, умеющий работать в распределённом режиме. И ничего не нашли. «Конечно же, такое решение наверняка уже есть», — сказали мы и пошли гуглить…. 12. Прошерстив документацию по разным statsd (https://github.com/etsy/statsd/wiki#server-implementations на момент 11. Видимо, ни разработчики, ни пользователи этих решений с ТАКИМ количеством метрик пока ещё не сталкивались, иначе они бы обязательно что-нибудь придумали. 2017), мы не нашли ровным счётом ничего.

Зачем? И тут мы вспомнили про «игрушечный» statsd — bioyino, который писали на хакатоне just for fun (название проекта сгенерировал скрипт перед началом хакатона) и поняли, что нам срочно нужен собственный statsd.

  • потому что в мире слишком мало клонов statsd,
  • потому что можно обеспечить желаемую или близкую к желаемой отказоустойчивость и масштабируемость (в том числе синхронизировать агрегированные метрики между серверами и решить проблему конфликтов при отправке),
  • потому что можно считать метрики точнее, чем это делает brubeck,
  • потому что можно самим собирать более детальную статистику, которую brubeck нам практически не предоставлял,
  • потому что предоставился шанс запрограммировать свой собственный хайперформансдистрибьютедскейлаблапликейшен, который не будет полностью повторять архитектуру другого такого же хайперфор… нувыпонели.

Конечно же, на Rust. На чём писать? Почему?

  • потому что уже был прототип решения,
  • потому что автор статьи на тот момент уже знал Rust и рвался написать на нём что-нибудь для продакшена с возможностью выложить это в open-source,
  • потому что языки с GC нам не подходят в силу природы получаемого трафика (практически realtime) и GC-паузы практически недопустимы,
  • потому что нужна максимальная производительность, сравнимая с C
  • потому что Rust предоставляет нам fearless concurrency, а начав писать это на C/C++, мы бы огребли ещё больше, чем у brubeck, уязвимостей, переполнений буфера, race conditions и других страшных слов.

У компании не было опыта создания проектов на Rust, и сейчас мы тоже не планируем использовать его в основном проекте. Был и аргумент против Rust. Поэтому были серьёзные опасения, что ничего не получится, но мы решили рискнуть и попробовали.

Шло время…

Что получилось? Наконец, после нескольких неудачных попыток, первая работающая версия была готова. Получилось вот такое.

Ноды соединены между собой некоторым протоколом распределённой блокировки (distributed lock), который позволяет выбрать среди них ту единственную (здесь мы плакали), которая достойна отправлять метрики Великому. Каждая нода получает свой собственный набор метрик и накапливает их у себя, причём не агрегирует метрики для тех типов, где для финальной агрегации потребуется их полный набор. Кроме консенсуса, ноды достаточно часто (по умолчанию один раз в секунду) досылают своим соседям те части предагрегированных метрик, которые удалось за эту секунду набрать. В данный момент эта проблема решается средствами Consul, но в будущем амбиции автора простираются до собственной реализации Raft, где той самой достойной будет, конечно же, нода-лидер консенсуса. Несмотря на довольно большое количество входящих метрик, накопление требует совсем немного памяти и ещё меньше CPU. Получается, что масштабирование и отказоустойчивость сохраняются — каждая из нод по-прежнему держит у себя полный набор метрик, но метрики при этом отправляются уже агрегированными, по TCP и с кодированием в бинарный протокол, поэтому расходы на дублирование по сравнению с UDP значительно снижаются. Дополнительным бонусом получаем отсутствие лишних перезаписей данных в Graphite, как это было в случае с burbeck. Для наших хорошо сжимаемых мертик это всего лишь несколько десятков мегабайт данных.

Само собой, сетевая железяка не разбирает содержимое пакетов и поэтому может потянуть гораздо больше, чем 4M пакетов в секунду, не говоря уже о метриках, про которые она вообще ничего не знает. UDP-пакеты с метриками разбалансированы между нодами на сетевом оборудовании через простой Round Robin. В случае падения сервера сетевое устройство быстро (в течение 1-2 секунд) обнаруживает этот факт и убирает упавший сервер из ротации. Если учесть, что метрики приходят не по одной в каждом пакете, то проблем с производительностью в этом месте мы не предвидим. не являющиеся лидером) ноды можно включать и выключать практически не замечая просадок на графиках. В результате этого пассивные (т.е. Внезапная потеря/выключение/переключение лидера по-прежнему нарисует незначительную аномалию (30-секундный интервал по-прежнему рассинхронизирован), но при наличии связи между нодами можно свести к минимуму и эти проблемы, например, путём рассылки синхронизирующих пакетов. Максимум, что мы теряем — это часть метрик, пришедших за последнюю секунду.

Приложение, конечно же, многопоточное, но архитектура потоков отличается от той, что использована в brubeck. Немного о внутреннем устройстве. В bioyino рабочие потоки (workers) разделены на две группы: ответственные за сеть и ответственные за агрегацию. Потоки в brubeck — одинаковые — каждый из них отвечает одновременно и за сбор информации, и за агрегацию. В данный момент на наших серверах мы работаем в 8 сетевых и 4 агрегирующих потока. Такое разделение позволяет более гибко управлять приложением в зависимости от типа метрик: там где требуется интенсивная агрегация, можно прибавить агрегаторов, там где много сетевого трафика — прибавить количество сетевых потоков.

Заполненные сетевыми потоками буферы распределяются между считающими потоками, где в дальнейшем парсятся и агрегируются. Считающая (ответственная за агрегацию) часть достаточно скучная. Всё это, включая пересылку данных между нодами и работу с Consul, выполняется асинхронно, работает на фреймворке tokio. По запросу метрики отдаются для отправки на другие ноды.

Основной задачей выделения сетевых потоков в отдельные сущности было стремление уменьшить время, которое поток затрачивает не на чтение данных из сокета. Гораздо больше проблем при разработке вызвала сетевая часть, ответственная за приём метрик. Поэтому сейчас используется recvmmsg с большими буферами (а буфера, господа офицеры, это вам не абы что!). Варианты с использованием асинхронного UDP и обычного recvmsg быстро отпали: первый ест слишком много user-space CPU на обработку событий, второй — слишком много переключений контекста. В режиме multimessage удаётся достичь главного: подавляющее большинство времени сетевой поток разгребает очередь ОС — вычитывает данные из сокета и перекладывает их в userspace-буфер, только изредка переключаясь на то, чтобы отдать заполненный буфер агрегаторам. Поддержка обычного UDP оставлена для ненагруженных случаев, когда в recvmmsg нет необходимости. Очередь в сокете практически не накапливается, количество отброшенных пакетов практически не растёт.

Примечание

Если вы вдруг решите опробовать сервер самостоятельно, то, возможно, столкнётесь с тем, что после отправки маленького количества метрик, они не прилетят в Graphite, оставшись в буфере сетевого потока. В настройках по умолчанию размер буфера выставлен достаточно большим. Для работы с небольшим количеством метрик нужно выставить в конфиге bufsize и task-queue-size значения поменьше.

Напоследок — немного графиков для любителей графиков.

Статистика количества входящих метрик по каждому серверу: более 2 млн MPS.

Отключение одной из нод и перераспределение входящих метрик.

Статистика по исходящим метрикам: отправляет всегда только одна нода — рейдбосс.

Статистика работы каждой ноды с учетом ошибок в различных модулях системы.

Детализация входящих метрик (имена метрик скрыты).

Конечно же писать код, бл...! Что мы планируем с этим всем делать дальше? В ближайших планах — переход на собственную версию Raft, смена peer-протокола на более переносимый, внесение дополнительной внутренней статистики, новых типов метрик, исправление ошибок и другие улучшения. Проект изначально планировался как open-source и останется таким всю его жизнь.

д. Конечно же, приветствуются все желающие помогать в развитии проекта: создавайте PR, Issues, по возможности будем отвечать, дорабатывать и т.

На этом, как говорится, that's all folks, покупайте наших слонов!

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»