Как мониторить Java-приложения: метрики, алерты и правило 80/20

Как мониторить Java-приложения: метрики, алерты и правило 80/20, image #1

Хороший мониторинг помогает быстро понять, что происходит с приложением и куда смотреть в первую очередь. Для этого не нужно пытаться измерить всё: базовый набор технических метрик покрывает большинство типовых проблем, а бизнес-метрики, SLO и анализ аномалий помогают заранее замечать нетипичные отклонения. В Календаре мы называем этот подход правилом 80/20.

Всем привет! Меня зовут Настя, я бэкенд-разработчик в Яндекс 360 и отвечаю за надёжность Календаря. В этой статье я покажу, какие метрики стоит взять за основу, как выбирать полезные алерты и чем дополнять базовый набор для оставшихся 20%.

Подписывайся на наши соц сети:

https://max.ru/javalib

https://t.me/javalib

https://vk.com/javatutorial

Почему перебор метрик убивает мониторинг

Если собрать разработчиков и спросить у них, хорошо ли, когда метрик много, то подавляющее большинство с этим тезисом согласится. К слову, я тоже. Нам хочется понимать, хорошо ли у нас всё работает, и вовремя узнавать, если где-то что-то идёт не так. Мы хотим кучу метрик, хотим оперативного подсвечивания инцидентов, вызова дежурных и прочего.

Это одна сторона медали. Вторая — не такая блестящая. В какой-то момент (особенно это характерно для зрелых продуктов) мы понимаем, что чем больше новых метрик мы добавляем, тем больше появляется информационного шума. Дашборды превращаются в нечто бездонное, куда не хочется даже заглядывать. А вот когда реально что-то случается, то никто не знает наверняка, на какой именно график и на каком дашборде надо смотреть, чтобы увидеть нужную метрику и её состояние. Например, когда у вас 1 000+ алертов, которые безудержно флапают.

Что делать? Как и везде в жизни, тут хорошо бы достичь золотой середины — добавить ровно столько метрик, сколько надо для получения пользы, но не столько, чтобы утонуть в них.

Два главных принципа здорового мониторинга

Принцип № 1. Всегда имейте в виду, что метрики — это маяки, которые сигнализируют о наличии проблемы, а не микроскопы, которые детально показывают вам, какая именно из шестерёнок огромного механизма барахлит. Или, если хотите другую аналогию, градусники. Градусник просто показывает, есть температура или нет, это бинарка. А вот уже на основании показаний градусника мы идём к врачу, который помогает поставить диагноз и назначить план лечения.

С алертами та же история: они просто показывают, есть проблема или нет, без указания на конкретную строку в коде, которую стоит починить

Принцип № 2. Нужно стремиться к балансу. Само собой, нам хочется замониторить все наши сервисы так, чтобы нигде и никогда не пропустить ни одну проблему. Однако очень наивно полагать, что такой дашборд на 1 000+ метрик будет полезен и что на него вообще будут смотреть. Так что тут нужен баланс, без этого никуда.

Базовые метрики

Как мониторить Java-приложения: метрики, алерты и правило 80/20, image #2

Почти любой бэкенд можно представить вот такой схемой. У нас тут есть синхронный API (у вас может не быть, но обычно есть). Бэк ходит в другие системы и другие бэки, может что-то складывать в очередь, что-то оттуда читать, что-то класть в БД или читать оттуда — в общем, взаимодействует.

RED-метрики бэкенда — HTTP/gRPC, клиентов, очередей и баз данных

Вернёмся к принципу 80/20, о котором я говорила в самом начале. Что это за цифра, 80%, и откуда она взялась?

Как показывает опыт, большинство инцидентов можно поймать базовым набором метрик. И лишь для по-настоящему сложных ситуаций вам пригодятся бизнес-специфичные метрики — те, для которых в целом прописана определённая бизнес-логика.

Вернёмся к схеме для наглядности.

За чем стоит обязательно следить
За чем стоит обязательно следить

Начну с HTTP/gRPC-сервера в нашем синхронном API. Тут золотые метрики — request, error и duration. Они могут быть вам знакомы как RED-метрики. Что тут важно — их можно применить почти для любой системы, которая так или иначе завязана на запросы извне.

Мы замеряем три вещи:

  1. Количество запросов. Тут мы можем увидеть, если кто-то решил спустить на нас DDoS, или, наоборот, перестал это делать, а также если у нас куда-то утекает трафик.
  2. Ошибки. Причём именно те ошибки, которые наш сервер отдаёт клиенту. В случае HTTP — это 4xx и 5xx, в случае gRPC — unavailable и прочее.
  3. Длительность запросов. Это помогает узнать, что сервер где-то начал подтормаживать или же, наоборот, очень быстро отдаёт что-то странное.

Если завести себе три таких графика, то можно будет понять практически любую проблему на стороне сервера.

Но кроме сервера есть ещё и клиент, для которого тоже работают эти метрики.

Как мониторить Java-приложения: метрики, алерты и правило 80/20, image #4

Когда наш бэк идёт в любую другую систему (да хоть в соседний бэк), мы делаем то же самое. Берём те же золотые RED-метрики и те же самые ошибки, смотрим на длительность запросов. Так точно можно заметить, если какой-то сервис, в который мы ходим, начал влиять на другой сервис, что повлекло проблемы.

Метрики очереди: producer, consumer и критическая важность лагов

Проблемы с очередями появляются не только когда мы пишем в них слишком много, но и когда пишем слишком мало.

Очереди — на что смотрим всегда
Очереди — на что смотрим всегда

И смотреть тут надо на такие же метрики — сколько сообщений пишем в секунду, стало их слишком много или слишком мало (запросы), по какой причине отвалилась сеть и мы перестали писать в очередь (ошибки). Ну и тайминги.

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

Не стоит недооценивать лаги: эта метрика важнее, чем кажется
Не стоит недооценивать лаги: эта метрика важнее, чем кажется

Так что это очень важная метрика для очередей — количество записанных сообщений минус количество прочитанных. Получаем разницу позиций в очереди.

Метрики баз данных

Тот же набор RED-метрик пригодится и для мониторинга состояния баз данных. Отдельно стоит следить за connection pool: успешные запросы к БД ещё не гарантируют, что с пулом соединений всё в порядке.

При этом важно охватывать всю цепочку. На стороне приложения есть клиентский пул, а между приложением и БД может работать отдельный серверный пул, например Odyssey. Если наблюдать только за одним из них, отклонение на другом уровне останется незаметным.

Клиентский и серверный connection pool нужно включать в общий контур мониторинга
Клиентский и серверный connection pool нужно включать в общий контур мониторинга

Метрики клиентского пула обычно можно подключить через Spring Boot и Micrometer. Если в архитектуре есть отдельный серверный пул, его нужно мониторить на стороне компонента, который этим пулом управляет. Такой контур помогает заметить риск заранее — до того, как он повлияет на запросы.

JVM-метрики — что Spring Boot даёт сразу, а что вам придётся писать руками

Итак, это было про бэкенд. Теперь давайте про его внутрянку — про Java.

Тут тоже есть свои золотые метрики, но их триада немного иная — это ресурсы: процессор, память и диск.

Топ-3 формата «Вечная классика»
Топ-3 формата «Вечная классика»

Вы, возможно, спросите, где же сеть, и будете правы: это тоже важная штука. Но увы, из Java-приложений очень сложно собирать сетевые метрики — их лучше собирать из Kubernetes. Так что остановимся на этих трёх. Их можно без проблем собирать с вашего контейнера и через Spring Boot.

Процессор — как вовремя распознать заблокированные потоки

Мы же не просто используем процессор, а запускаем на нём определённые потоки, у которых могут быть разные состояния. Например, поток может чего-то ждать, поток может что-то делать, а ещё поток может быть чем-то заблокирован.

Оцениваем состояние потоков
Оцениваем состояние потоков

В плане метрик это значит, что нам хорошо бы видеть и понимать: какие-то потоки внезапно заблокировались, зависли или вообще закончились. В Spring Boot можно смело смотреть на состояние всех потоков в сумме.

Но ещё у нас есть тред-пулы, которые тоже могут (причём независимо от системы) заканчиваться. Поэтому если мы хотим мониторить происходящие на пулах операции — например, в ForkJoinPool или каком-то другом пуле, то просто так этого сделать уже не получится. Их надо будет замониторить самостоятельно, включая и ForkJoinPool — он хоть и создаётся штатными средствами, но мониторить его надо самим.

Вот простая команда, после вызова которой в нашем регистре появятся метрики по пулам.

ExecutorServiceMetrics.monitor(
meterRegistry,
ForkJoinPool.commonPool() ,
"fork.join.pool.common"
);
ExecutorService myPool =
Executors.newFixedThreadPool (8);
ExecutorServiceMetrics-monitor(
meterRegistry,
myPool,
"my.executor.service"
) ;

Таким же образом можно обернуть каждый нужный вам thread pool, получив красивые графики.

Будет гораздо нагляднее
Будет гораздо нагляднее

Память — что и как может утечь здесь

Тут тоже просто не будет — мы же живём в JVM, значит память у нас managed, да ещё и делится на Heap и Non-Heap. Спасибо, что все области памяти и её регионы точно так же репортятся. Можно из коробки сразу пойти и посмотреть, сколько у нас памяти на Direct Buffers, к примеру.

Не Heap’ом единым
Не Heap’ом единым

Здесь важно помнить, что радостно утекать у нас может не только Heap, но и всё остальное, хотя все привыкли прежде всего следить за Heap. Те же Direct Buffers могут вполне себе утекать, пару лет назад была утечка в gRPC-java, так что те, кто обновил эту библиотеку до новой версии, могли внезапно обнаружить утечки памяти и постоянное падение приложений. Просто из-за того, что обновили библиотеку. Чтобы такое заметить, надо мониторить именно разные регионы памяти.

К слову о Heap: у нас есть целый garbage collector, который генерирует нам разные задержки. В Spring Boot мы можем уже привычным образом посмотреть, сколько пауз у нас было и сколько по времени они длились.

Как мониторить Java-приложения: метрики, алерты и правило 80/20, image #12

Задержки могут генерировать ещё и блокировки, safepoint’ы и многое другое. И вот их уже из коробки не помониторишь. Например, для тех же safepoint’ов надо писать свой собственный код — нужна JVM, которая использует Java Flight Recorder, и надо подписаться на события, сообщающие о завершении safepoint’ов.

Если вам нужен пример такого кода для измерения safepoint’ов — держите.

Как мониторить Java-приложения: метрики, алерты и правило 80/20, image #13

Имейте в виду, что JVM по умолчанию не даст вам понять, что именно это был за safepoint, нельзя отделить один тип от другого и непонятно, что тут было блокировкой, а что — тем же самым GC. Можно лишь увидеть, что что-то где-то задерживается. Почему — секрет.

Задержек же может быть великое множество, кроме safepoint’ов. Мы живём не просто в JVM, а на большой железке, на которой запущена и ОСь, и, скорее всего, развёрнут кубер со своим набором подов, а в подах — контейнеры, где и обитает наша JVM. И ещё повезёт, если всё это и правда работает просто на железке. А если в облаке, где таких слоёв и переменных становится ещё больше?

И это всё ещё не максимальное количество слоёв
И это всё ещё не максимальное количество слоёв

У разных слоёв — приложения, JVM, контейнера, оркестратора, ОС и инфраструктуры — свои наборы метрик и зоны ответственности. Поэтому мониторинг стоит строить в двух уровнях: подробная картина каждого слоя плюс общий обзор всей цепочки. Так сигналы можно сопоставлять между собой, не превращая дашборд в бесконечное полотно.

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

Скажем, если ваше приложение latency sensitive, вы увидите, что ваш P99 улетает куда-то в космос. А потом из этого космоса возвращается. Причина, скорее всего, будет как раз в одном из этих слоёв пирога.

Но решение есть. Чтобы смотреть на все такие задержки в сумме, создали такую прекрасную штуку, как hiccups. Идею изложил Gil Tene в далёком 2015-м, и он же написал библиотеку для их измерения.

В чём тут суть? Сколько времени будет выполняться код, который не делает ровным счётом ничего?

В идеальном мире ответом было бы «Нисколько». Но мы, увы, не в идеальном мире, и на этой метрике будут видны все возможные задержки на уровнях ниже этого кода. Причиной задержки мог стать шумный сосед в кубере, планировщик, который не выдал CPU, или что-то ещё. По этой метрике можно увидеть все слои сразу, даже не зная, какие слои вообще есть, и не имея доступа к конкретному. Вы сможете понять главное: или в вашем коде ошибка, или же что-то пошло не так слоем ниже. А потом сходить к ответственным за этот слой.

Увы, исходная библиотека jHiccup была рассчитана на то, чтобы писать логи на диск, а потом с этого диска красиво выводить куда-то данные. Но в 2026-м код принято писать с помощью Micrometer и выгружать в современные системы мониторинга, так что этот код надо адаптировать под реалии. Вот ссылки на исходный код и на переписанный для Micrometer — берите тот, что вам удобнее.

С базовыми метриками сложно перебрать — их в целом-то фиксированное количество. Почти все они есть из коробки, но Spring Boot не идеален, так что немного кода руками написать придётся.

Бизнес-метрики

Мы обсудили, как отлавливать 80% метрик. Теперь давайте посмотрим, как поймать остальные 20%, специфичных для бизнес-задач.

Самое главное — понять, чем занимается ваш сервис. У вас может быть множество важных бизнес-метрик, например:

  • число успешных заказов;
  • конверсии любого рода;
  • качество поисковой выдачи (если вы поисковый движок);
  • процент ошибок на HTTP/gRPC.

Как только вы поймёте, что в вашем сервисе самое важное и олицетворяет его успех, в ту сторону и надо копать.

Как выбрать бизнес-метрики

Способ 1 — отталкиваться от инцидентов. Например, вы определили, чем занимается ваш сервис, но тут случился какой-то инцидент, который вы не заметили на метриках. Недовольные из-за инцидента клиенты — это не только техническая проблема, но ещё и бизнесовая. Так что такие проблемы хочется видеть заранее.

Тут полезно будет посмотреть на ваши инциденты за последний месяц и отметить, когда они случились и что именно вы не заметили. Заведите метрику, алерт и график, чтобы такое не пропускать.

Создайте таблицу «Проблема — следы в инцидентах», чтобы оценить значимость метрики
Создайте таблицу «Проблема — следы в инцидентах», чтобы оценить значимость метрики

Базовый пример — клиенты жалуются, что у них не срабатывают скидочные купоны. Можно начать смотреть на процент ошибок при применении таких купонов. А ещё можно немного генерализовать имеющиеся метрики, за которыми вы уже следите, — брать что-то шире, чем конкретный инцидент с купоном. Скажем, замерить не только случай несрабатывания купона, но и любые проблемы с промокодами, скидками и вот такими смежными штуками.

ОК, вы выбрали метрики, дело за малым — завести алерты. Кажется, что тут вообще ничего сложного, просто прописать пороговые значения — и всё. Но нет.

Вот пример из Календаря. После одного из изменений сценарий создания встречи стал отправлять в смежный сервис заметно больше запросов, чем обычно. При этом технически всё работало: события создавались, запросы завершались успешно, ошибок не было. Если смотреть только на error rate, такое изменение останется незаметным. А вот график межсервисного трафика сразу показывал аномалию. Этот кейс хорошо иллюстрирует ограничение базовых метрик: успешный запрос ещё не означает, что система ведёт себя ожидаемо. Поэтому важно следить не только за ошибками, но и за отклонениями от нормального профиля нагрузки.

Поэтому мы мониторим не только пороговые значения, но и возможные аномалии. У Prometheus, к счастью для всех нас, есть готовое решение.

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

Это уже готовое решение, вам просто надо на метрику повесить лейбл и указать, что ваша аномалия будет называться, скажем, demo request. Тип этой аномалии — именно реквесты, и это не какая-то gauge-метрика, которая может расти от 0 до 100 в процентах, а реквесты, которых может быть любое количество, вплоть до бесконечности.

- record: anomaly:request:rate5m
expr: sum(rate(duration_milliseconds_count[5m])) by (job)
labels:
anomaly_name: "otel_demo_requests"
anomaly_type: "requests

В самом же алерте указываем, что хотим его зажигать, если метрика находится ниже или выше границы аномалии (то есть выбивается из допустимого диапазона).

- alert: AnomalyDetected
for: 5m
expr: |
metric < lower_band
or
metric > upper_band

Визуально на графике это будет выглядеть примерно так:

Что мы видим: у нас была метрика, которая всегда была где-то в районе нуля, а затем начала резко куда-то расти. Видно, что аномалия зажигается именно в момент роста и немного после него. А затем уже считается, что аномалии (роста) нет и метрика снова в какой-то константе (просто новой). Это даёт вам возможность увидеть, что где-то что-то произошло, но при этом не получить значительных флапов, потому что сегодня это была аномалия, а завтра её просто не будет в то же время.

Способ 2 — определить SLO (Service Level Objective). То, что ваш сервис должен выполнять с точки зрения бизнеса. В контексте бизнес-метрик Календаря это договорённости вида «95% запросов на создание встречи завершаются за 200 мс или меньше».

Почему это полезно: так мы получаем фиксированный порог того, что можно считать проблемой. Само собой, как программисты, мы хотим, чтобы всё обрабатывалось за 0 мс и было 100% запросов без ошибок и задержек, но реальный мир вносит коррективы. Так что договаривайтесь с бизнесом о таких цифрах и делайте это с умом.

Onepager и drilldown для ускорения диагностики

Итак, нужные метрики выбраны. Теперь разберёмся, как разместить их на дашборде и не перегрузить его.

Есть две техники.

Первая — onepager: обзорный дашборд, который помещается на одном экране. Он помогает быстро оценить состояние системы и понять, куда перейти за деталями.

Цвет помогает быстро оценить состояние системы
Цвет помогает быстро оценить состояние системы

На onepager каждый компонент можно показать отдельным цветовым индикатором. Красный означает, что нужна быстрая реакция, жёлтый — что есть отклонение, которое стоит проверить, зелёный — что всё работает штатно

Вторая — drill-down: переход от общего индикатора к подробным метрикам конкретного компонента

Каждый индикатор ведёт к более подробному дашборду
Каждый индикатор ведёт к более подробному дашборду

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

Демонстративный пример нагляднее:

Как мониторить Java-приложения: метрики, алерты и правило 80/20, image #18

На дашборде выбрано пять разных функциональностей, я вижу тайминги и проценты ошибок. Красные клетки — я хочу посмотреть на read scheduler, кликаю на неё и перехожу на дашборд моего сервиса, который именно за это и отвечает.

На верхнем дашборде показаны пять функций, их время ответа и доля ошибок. Индикатор read scheduler показывает отклонение — открываем дашборд связанного сервиса. Там видно, что выросло время ответа БД и сервера; дальше можно перейти к графикам конкретных баз данных.

Делаем алерты понятными с помощью ранбуков

Наверняка вам не хочется оказаться в ситуации, когда алерты флапают, зажигаются, дежурный не спит ночами и не может уже смотреть на всё это.

Что делать? Использовать ранбуки!

Если алерт загорелся, должно быть сразу понятно, что с ним делать. Ситуации, когда алерт загорелся, но никто не знает, что делать, — это ситуации про плохой алерт, их в идеале и быть-то не должно.

Ранбук подразумевает, что у каждого алерта должно быть подробное описание, инструкция, которой сможет воспользоваться любой разработчик, независимо от его опыта и степени погружённости в продукт.

Вот пример. Алерт про то, что мой сервис стал потреблять более 85% CPU. Я расписываю шаги.

  • Прежде всего я хочу проверить релизы и запуски, вдруг там что-то недавно менялось. Если да — скорее всего, захочу откатиться на версию без проблем, чтобы убрать фактор релиза.
  • Проверю RPS. Если сервис под DDoS, то рост CPU — это норма, можно найти виновника, пожаловаться на него или включить rate limiter. А может, это вообще garbage collector.
  • Проверю использование памяти. Если там что-то подозрительное, сниму heap dump.

И так с каждым алертом. А если придумать ранбук не получается — подумайте, нужен ли вам вообще этот алерт.

Небольшой итоговый чек-лист

  • Подключайте к вашему приложению Spring Boot Actuator: там очень много полезного.
  • Следите за hiccups — просто чтобы замечать все возможные задержки на всех уровнях ниже приложения и понимать, что у вас, например, заканчиваются ресурсы.
  • Используйте анализ аномалий — отличная вещь для того, чтобы увидеть, что что-то идёт не так, но недостаточно «не так», чтобы затриггерить какой-то из ваших текущих алертов.
  • Пересмотрите свои дашборды. Скорее всего, вам тоже захочется сделать их иерархическими, чтобы в них было проще ориентироваться.
  • Пишите ранбуки. Они сделают жизнь проще. Гораздо проще.

Источник

131 views·8 shares