Всем привет! За последние пять лет мы достаточно часто сталкивались с запросами на мониторинг Java приложений. Собрав основные моменты по мониторингу Java, мы решили написать данный пост.
Сегодня мы с вами рассмотрим, что такое full-stack мониторинг и чем он отличается от привычного «уху» понятию мониторинга, нюансы full-stack мониторинга для Java и сложности мониторинга микросервисных приложений на Java. Расскажем, как мы реализуем full-stack мониторинг с помощью open-source стандартов и платной платформы.
![](https://habrastorage.org/getpro/habr/upload_files/5f1/484/cea/5f1484ceaa77bed9c2ee7b436ae72b5b.png)
Full-stack мониторинг
Давайте определимся, что мы называем full-stack мониторингом?
Full-stack мониторинг — это подход в мониторинге производительности приложений, который подразумевает под собой мониторинг всего стека, что включает в себя:
Мониторинг приложений — сбор метрик приложения, сбор трейсов транзакций, обеспечение видимости на уровне кода и т.д.
Мониторинг инфраструктуры — метрики хостов, процессов, контейнеров и т.д.
Мониторинг конечных пользователей — сбор метрик с браузера пользователя, мобильного приложения, синтетические проверки и т.д.
Основное отличие full-stack мониторинга от привычного мониторинга заключается в том, что, применяя full-stack мониторинг, мы получаем реальное представление о том, как себя "чувствует” наше приложение и как его работа влияет на пользовательский опыт, так как видим не просто метрики приложения, а влияние всех инфраструктурных компонентов на приложение и то, как это сказывается на опыте наших пользователей.
Зачастую под full-stack мониторингом подразумевают продукт/решение, который предоставляет единый сервис для сбора, хранения и анализа собираемых данных с приложения, инфраструктуры и пользователей. Но в нашей статье мы поговорим именно про подход и расскажем про использование как open-source стандартов, так и платной платформы для реализации full-stack мониторинга Java.
По результатам нашего опыта можем сказать, что применение full-stack подхода к мониторингу позволяет заметно ускорить процесс root cause анализа инцидентов и действительно снизить MTTR.
Нюансы full-stack мониторинга Java
Чтобы понимать что нужно мониторить в Java приложениях, давайте для начала разберемся что собой представляет Java. Во-первых, Java — это приложения, написанные не только на языке программирования Java, но и на других языках, которые основываются на Java – Kotlin, Scala, JRuby, и другие.
Во-вторых, Java — это еще и среда для чтения, понимания и выполнения байт-кода Java. Приложения, написанные на Java языке или на языках, базирующихся на Java, компилируются в байткод и запускаются в определенной среде исполнения – JVM (Java Virtual Machine).
Поэтому нам нужно мониторить как само приложение на Java, так и среду, в которой оно запускается.
В целом full-stack мониторинг Java будет выглядеть следующим образом:
Сбор Java и JVM метрик.
Сбор распределенных трассировок транзакций.
Профилирование Java кода.
Мониторинг конечных пользователей.
Давайте подробнее пройдемся по всем компонентам full-stack мониторинга Java.
Сбор метрик Java и Java Virtual Machine
Под сбором метрик Java подразумевается сбор уникальных метрик с используемых фреймворков Java (SpringBoot, DropWizzard и т.д.) и applications серверов (JBoss/Wildfly, Websphere и т.д.), например:
количество HTTP Requests для SpringBoot Framework;
количество WebDeployments и количество их активных сессий для JBoss;
количество запросов и среднее время отклика servlets для Websphere.
Получить метрики Java Virtual Machine (JVM) можно с помощью имеющейся у Java технологии JMX (Java Management Extensions), которая представляет информацию о состоянии самой JVM, garbage collection и других внутренних элементов.
Какие метрики JVM нас интересуют в первую очередь:
Memory Usage
Общий объем памяти, используемый самой JVM.
Получим изjava.lang.Runtime#totalMemory
Threads
Количество тредов, находящихся в статусах:new
,runnable
,timed-waiting
,waiting
илиblocked
.Текущие идентификаторы тредов получим из
java.lang.management.ThreadMXBean#getAllThreadIds
Статус тредов получим из
ThreadMXBean#getThreadInfo
Heap Memory
Общее использование heap memory самой JVM.Текущее значение получим из
java.lang.Runtime#totalMemory - java.lang.Runtime#freeMemory
Максимальное значение получим из
java.lang.Runtime#maxMemory
Memory Pools
Использование memory poolsИнформацию о пуле получим из
ManagementFactory#getMemoryPoolMXBeans
Значение использования памяти пулом получим из
java.lang.management.MemoryUsage
Максимальное значение получим из
getMax
, начальное значение получим изgetInit
, и текущее значение получим изgetUsage
.
Garbage Collection
Значение garbage collection и время его выполненияИнформацию о garbage collection получим из
ManagementFactory#getGarbageCollectorMXBeans
Значение для каждого коллектора получим из
java.lang.management.GarbageCollectorMXBean
Garbage collection runtime
получим изgetCollectionTime
Во время разработки приложений, самыми популярными инструментами для сбора и анализа метрик являются VisualVM и JDK Mission Control.
![](https://habrastorage.org/getpro/habr/upload_files/157/375/899/157375899c388ea918f980325f9b8281.png)
Помимо стандартного набора метрик, можно описать свои собственные (кастомные) метрики и здесь нам помогут такие инструменты, как DropWizard, Micrometer, StatsD и Prometheus.
На нашей практике, чаще всего используется Prometheus для описания кастомных метрик. Prometheus предоставляет большой набор интеграций и оберток для большинства популярных фреймворков, основанных на Java. Благодаря чему, Prometheus достаточно просто и быстро интегрируется с приложением.
У всех вышеупомянутых инструментов есть один большой недостаток – метрики изолированы друг от друга из-за чего отсутствует контекст данных, который нам необходим для расследования инцидентов, особенно если у нас микросервисная среда. Но к этому мы вернемся чуть позже, а пока перейдем к трейсингу запросов.
Сбор распределенных трассировок (distributed tracing)
Чтобы обеспечить быстрое выявление и решение проблем с Java приложениями, одного сбора метрик будет недостаточно, необходимо видеть, как все наши сервисы взаимодействуют между собой, то есть собирать информацию о наших транзакциях. Это возможно сделать с помощью end-to-end трейсинга всех вызовов в нашем Java приложении.
Наиболее распространенный путь в получении end-to-end трейсинга — это использование open-source стандартов, например, таких как, OpenTelemetry и OpenTracing.
![](https://habrastorage.org/getpro/habr/upload_files/216/ef0/c10/216ef0c10ef389a36fa3a9d658d7de2d.png)
Пример видимости транзакции в одном из самых распространенных open-source инструментов для end-to-end трейсинга – Jaeger
Но важно учитывать, что, используя разные open source решения - одно для сбора метрик (например, Prometheus), другое для сбора транзакций (Jaeger, Zipkin и т.п.) мы столкнемся с необходимостью ручной корреляции метрик и трейсов между собой. Связанно это с тем, что все open source решения по-разному записывают и хранят данные. И если инцидент затронул много сервисов, что часто происходит в микросервисных инфраструктурах, то при использовании такой связки, как например Prometheus + Jaeger, процесс root-cause анализа инцидента наоборот может усложниться и в результате увеличиться MTTR.
Поэтому для сбора метрик, транзакций и их корреляции между собой в микросервисных инфраструктур, мы используем готовые продукты для full-stack мониторинга, ниже расскажем про их применение.
Профилирование Java кода
Чтобы понять причину, почему наше Java приложение потребляет много ресурсов, нам нужно видеть, какой именно метод приложения вызывает проблемы с производительностью. Поэтому нужно производить профилирование Java кода.
Профилировщики существуют уже очень давно. Первые профилировщики использовались только на этапе отладки приложения, в тестовой среде, из-за их высокого потребления CPU и Memory ресурсов. Но сейчас существуют профилировщики, которые не несут такой высокой нагрузки, и мы можем их запускать уже в продакшен среде.
![](https://habrastorage.org/getpro/habr/upload_files/1fd/7d3/ff4/1fd7d3ff4fbab3ab29b379a2b4c245b8.jpg)
На нашей практике, для профилирования приложения в основном используются такие инструменты, как VisualVM, jProfiler и JavaMissionControl в сочетании с Flight Recorder.
Мониторинг конечных пользователей (End user мониторинг/Real user мониторинг)
Как мы писали выше, одной из важных частей full-stack мониторинга является мониторинг наших конечных пользователей. Это обусловлено тем, что работа нашего приложения напрямую влияет на пользовательский опыт. Пользователи взаимодействуют с приложением через веб-сайт или мобильное приложение и понимание того какие запросы от пользователей уходят на бэкенд, т.е. запросы к Java приложению, обеспечивают сквозную видимость запроса, от «клика» пользователя в браузере или мобильном приложение до конкретной транзакции на бэкенде.
Таким образом, мы видим влияние скорости работы и ошибок в Java приложение на пользовательский опыт.
Мы решили сейчас много не писать на тему end user мониторинга так как мы ранее уже достаточно подробно раскрыли эту тему в одном из наших постов:
Сложности мониторинга микросервисных Java приложений
Не могли не затронуть, хотя бы вскользь, тему мониторинга микросервисных Java приложений.
Основная сложность мониторинга таких приложений заключается в том, что увеличивается количество подконтрольных компонентов – контейнеры, оркестраторы (k8s, docker swarm, openshift и т.д). Контейнеры с приложениями постоянно скейлятся, могут запускать только на короткий срок времени. Все это экспоненциально усложняет процесс мониторинга.
Динамичность микросервисной архитектуры Java приложения приводит нас к тому, что для обеспечения full-stack мониторинга нам необходимо проводить постоянную инвентаризацию инфраструктуры c целью получения актуальных данных о нашей инфраструктуре. Это возможно реализовать с помощью autodiscovery подхода .
Под автодискаверингом принято понимать процесс непрерывного, автоматического обнаружения компонентов инфраструктуры с целью сбора ключевых данных о каждом из компонентов. В зависимости от типа компонента – приложение, база данных, балансировщик, очередь и т.д., данные будут собираться разные, но важно, чтобы они связывались между собой.
![Визуализация стека компонентов с их ключевыми метриками в продукте Instana Визуализация стека компонентов с их ключевыми метриками в продукте Instana](https://habrastorage.org/getpro/habr/upload_files/6ea/c8e/f1e/6eac8ef1e25e0b094ca3dafcf2374bd1.png)
Отсутствие автодискаверинга как неотъемлемой части full-stack мониторинга микросервисных приложениях приводит к тому, что вновь запущенные компоненты, могут быть не «замониторены» и в результате у нас появятся слепые зоны в инфраструктуре. Мы не будем иметь представления о том, как себя действительно «чувствует» тот или иной компонент инфраструктуры, не сможем корректно оценить состояние приложения.
Реализовать подход автодискаверинга вручную, достаточно трудоемкий процесс, поэтому в наших проектах мы используем готовое решение – Instana.
Full-stack мониторинг как продукт
Выше мы с вами рассмотрели full-stack мониторинг как подход, здесь же мы поговорим о full-tack мониторинге как готовом к использованию продукте.
На рынке существуют различные решения для full-stack мониторинга - Dynatrace, DataDog, New Relic, AppDynamics, Instana и т.д. Но мы расскажем именно про Instana, так она на данный момент объединяет в себе объемный функционал, необходимый для full-stack мониторинга как классических инфраструктур, так и микросервисных и выгодно отличается по цене от конкурентов.
С помощью Instana мы получаем full-stack мониторинг в том числе и для Java приложений без необходимости ручной настройки.
Instana постоянно и автоматически собирает ключевые данные для поддерживаемых технологий, относительно JVM обнаруживает все что находится внутри нее:
тип JVM;
версия JVM;
используемый framework;
коннекторы к базам данным;
Upstream и Downstream сервисы;
и многое другое.
Причем для агента Instana не важно в какой инфраструктуре запущено приложение – в Docker, Kubernetes, OpenShift, Linux или Windows. После обнаружения JVM агент подключается к самому процессу и анализирует архитектуру сервиса, какой Framework используется (Spring Boot, DropWizzard и так далее), сервер приложений (Jboss/Wildfly, Websphere, и тд) и коннекторы к базам данных. Далее приложение автоматически инструментируется и агентом начинают собираться ключевые метрики и трейсы приложения. Очень важно, что при этом не требуется перезапуск приложения.
Понимание топологии приложения позволяет Instana построить логическую модель всего стека, обнаружить сервисы и их зависимости.
Давайте посмотрим на пример топологической модели Spring Boot приложения:
Sping Boot приложение, это часть ->
Java процесса, который представляет собой Java приложение, запущенное в ->
JVM, которая запущена в ->
Docker контейнере, который запущен в ->
процессе операционной системы, который запущен на ->
Linux хосте.
Представив топологию в виде вертикального стека зависимостей компонентов инфраструктуры, с каждого уровня необходимо собрать уникальные метрики. И Instana собирает метрики с каждого уровня и визуализирует весь стек.
![](https://habrastorage.org/getpro/habr/upload_files/2d0/031/415/2d00314153ced86f160755a79c2626d7.png)
Но как мы писали выше, метрик недостаточно для полноценного мониторинга Java приложений. Приложения предоставляют собой сервисы, которые между собой как-то взаимодействуют. Instana обнаруживает все вызовы между сервисами и создает распределенные трейсы, показывая end-to-end карту вызовов.
![](https://habrastorage.org/getpro/habr/upload_files/dbe/1b4/03e/dbe1b403e6ff8c13debe1b28d71fcec1.png)
Посмотрев на карту вызовов, возникает необходимость проанализировать трейсы проблемного сервиса. И мы можем более детально проанализировать каждый из вызовов. Instana не сэмплирует вызовы, что позволяет анализировать абсолютно каждый из них.
![](https://habrastorage.org/getpro/habr/upload_files/dc9/f68/c5c/dc9f68c5c507e58e9453e3cc44ed58db.png)
Важной частью мониторинга Java приложений является профилирование кода. У нас могут быть метрики всех компонентов приложения и видимость всех вызовов, и мы можем понять связана ли эта проблема с использованием ресурсов и какой сервис именно пострадал. Но мы не узнаем, какой именно метод в работающем сервисе приложения вызывал проблемы с производительностью. И тут нам поможет постоянно работающий Java профилировщик. Профилировщик покажет точный метод(ы), которые перегружают CPU или имеют большой wait time.
![](https://habrastorage.org/getpro/habr/upload_files/e34/cf9/1bc/e34cf91bc1c100aa438f543d7f2f4229.png)
Подводя итоги
Актуальность full-stack мониторинга будет только расти и это обусловлено, в том числе, переходом на микросервисную архитектуру, которая экспоненциально усложняет мониторинг по причине постоянного увеличения подконтрольных компонентов инфраструктуры.
Full-stack мониторинг позволяет охватить весь наш стек – приложения, инфраструктуру, пользователей, и значительно ускорить root-cause анализ и снизить MTTR.
Имплементировать full-stack мониторинг возможно и как с помощью подхода “сделай сам”, используя open source стандарты (Prometheus/statsD для сбора метрик, Jaeger/Zipkin для сбора трейсов, jProfiler/Java VisualVM для профилирования Java кода), так и с помощью готовых продуктов, которых на рынке сейчас уже не мало.
Будем рады, если в комментариях вы поделитесь применяете ли full-stack мониторинг, как мониторите Java приложения и какие используете для этого инструменты?