Всем привет! За последние пять лет мы достаточно часто сталкивались с запросами на мониторинг Java приложений. Собрав основные моменты по мониторингу Java, мы решили написать данный пост. Сегодня мы с вами рассмотрим, что такое full-stack мониторинг и чем он отличается от привычного «уху» понятию мониторинга, нюансы full-stack мониторинга для Java и сложности мониторинга микросервисных приложений на Java. Расскажем, как мы реализуем full-stack мониторинг с помощью open-source стандартов и платной платформы. Давайте определимся, что мы называем full-stack мониторингом? Full-stack мониторинг — это подход в мониторинге производительности приложений, который подразумевает под собой мониторинг всего стека, что включает в себя: Мониторинг приложений — сбор метрик приложения, сбор трейсов транзакций, обеспечение видимости на уровне кода и т.д. Мониторинг инфраструктуры — метрики хостов, процессов, контейнеров и т.д. Мониторинг конечных пользователей — сбор метрик с браузера пользователя, мобильного приложения, синтетические проверки и т.д. Основное отличие full-stack мониторинга от привычного мониторинга заключается в том, что, применяя full-stack мониторинг, мы получаем реальное представление о том, как себя "чувствует” наше приложение и как его работа влияет на пользовательский опыт, так как видим не просто метрики приложения, а влияние всех инфраструктурных компонентов на приложение и то, как это сказывается на опыте наших пользователей.  Зачастую под full-stack мониторингом подразумевают продукт/решение, который предоставляет единый сервис для сбора, хранения и анализа собираемых данных с приложения, инфраструктуры и пользователей. Но в нашей статье мы поговорим именно про подход и расскажем про использование как open-source стандартов, так и платной платформы для реализации full-stack мониторинга Java. По результатам нашего опыта можем сказать, что применение full-stack подхода к мониторингу позволяет заметно ускорить процесс root cause анализа инцидентов и действительно снизить MTTR. Чтобы понимать что нужно мониторить в 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 (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. Помимо стандартного набора метрик, можно описать свои собственные (кастомные) метрики и здесь нам помогут такие инструменты, как DropWizard, Micrometer, StatsD и Prometheus.  На нашей практике, чаще всего используется Prometheus для описания кастомных метрик. Prometheus предоставляет большой набор интеграций и оберток для большинства популярных фреймворков, основанных на Java. Благодаря чему, Prometheus достаточно просто и быстро интегрируется с приложением. У всех вышеупомянутых инструментов есть один большой недостаток – метрики изолированы друг от друга из-за чего отсутствует контекст данных, который нам необходим для расследования инцидентов, особенно если у нас микросервисная среда. Но к этому мы вернемся чуть позже, а пока перейдем к трейсингу запросов.  Чтобы обеспечить быстрое выявление и решение проблем с Java приложениями, одного сбора метрик будет недостаточно, необходимо видеть, как все наши сервисы взаимодействуют между собой, то есть собирать информацию о наших транзакциях. Это возможно сделать с помощью end-to-end трейсинга всех вызовов в нашем Java приложении.  Наиболее распространенный путь в получении end-to-end трейсинга — это использование open-source стандартов, например, таких как, OpenTelemetry и OpenTracing. Пример видимости транзакции в одном из самых распространенных open-source инструментов для end-to-end трейсинга – Jaeger  Но важно учитывать, что, используя разные open source решения - одно для сбора метрик (например, Prometheus), другое для сбора транзакций (Jaeger, Zipkin и т.п.) мы столкнемся с необходимостью ручной корреляции метрик и трейсов между собой. Связанно это с тем, что все open source решения по-разному записывают и хранят данные. И если инцидент затронул много сервисов, что часто происходит в микросервисных инфраструктурах, то при использовании такой связки, как например Prometheus + Jaeger, процесс root-cause анализа инцидента наоборот может усложниться и в результате увеличиться MTTR.  Поэтому для сбора метрик, транзакций и их корреляции между собой в микросервисных инфраструктур, мы используем готовые продукты для full-stack мониторинга, ниже расскажем про их применение. Чтобы понять причину, почему наше Java приложение потребляет много ресурсов, нам нужно видеть, какой именно метод приложения вызывает проблемы с производительностью. Поэтому нужно производить профилирование Java кода. Профилировщики существуют уже очень давно. Первые профилировщики использовались только на этапе отладки приложения, в тестовой среде, из-за их высокого потребления CPU и Memory ресурсов. Но сейчас существуют профилировщики, которые не несут такой высокой нагрузки, и мы можем их запускать уже в продакшен среде. На нашей практике, для профилирования приложения в основном используются такие инструменты, как VisualVM, jProfiler и JavaMissionControl в сочетании с Flight Recorder. Как мы писали выше, одной из важных частей full-stack мониторинга является мониторинг наших конечных пользователей. Это обусловлено тем, что работа нашего приложения напрямую влияет на пользовательский опыт. Пользователи взаимодействуют с приложением через веб-сайт или мобильное приложение и понимание того какие запросы от пользователей уходят на бэкенд, т.е. запросы к Java приложению, обеспечивают сквозную видимость запроса, от «клика» пользователя в браузере или мобильном приложение до конкретной транзакции на бэкенде.  Таким образом, мы видим влияние скорости работы и ошибок в Java приложение на пользовательский опыт.  Мы решили сейчас много не писать на тему end user мониторинга так как мы ранее уже достаточно подробно раскрыли эту тему в одном из наших постов: End User Monitoring — контролируем производительность фронтенда с помощью Instana.  Не могли не затронуть, хотя бы вскользь, тему мониторинга микросервисных Java приложений. Основная сложность мониторинга таких приложений заключается в том, что увеличивается количество подконтрольных компонентов – контейнеры, оркестраторы (k8s, docker swarm, openshift и т.д). Контейнеры с приложениями постоянно скейлятся, могут запускать только на короткий срок времени. Все это экспоненциально усложняет процесс мониторинга.   Динамичность микросервисной архитектуры Java приложения приводит нас к тому, что для обеспечения full-stack мониторинга нам необходимо проводить постоянную инвентаризацию инфраструктуры c целью получения актуальных данных о нашей инфраструктуре. Это возможно реализовать с помощью autodiscovery подхода .  Под автодискаверингом принято понимать процесс непрерывного, автоматического обнаружения компонентов инфраструктуры с целью сбора ключевых данных о каждом из компонентов. В зависимости от типа компонента – приложение, база данных, балансировщик, очередь и т.д., данные будут собираться разные, но важно, чтобы они связывались между собой. Отсутствие автодискаверинга как неотъемлемой части full-stack мониторинга микросервисных приложениях приводит к тому, что вновь запущенные компоненты, могут быть не «замониторены»  и в результате у нас появятся слепые зоны в инфраструктуре.  Мы не будем иметь представления о том, как себя действительно «чувствует» тот или иной компонент инфраструктуры, не сможем корректно оценить состояние приложения.  Реализовать подход автодискаверинга вручную, достаточно трудоемкий процесс, поэтому в наших проектах мы используем готовое решение – Instana. Выше мы с вами рассмотрели 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 собирает метрики с каждого уровня и визуализирует весь стек. Но как мы писали выше, метрик недостаточно для полноценного мониторинга Java приложений. Приложения предоставляют собой сервисы, которые между собой как-то взаимодействуют. Instana обнаруживает все вызовы между сервисами и создает распределенные трейсы, показывая end-to-end карту вызовов. Посмотрев на карту вызовов, возникает необходимость проанализировать трейсы проблемного сервиса. И мы можем более детально проанализировать каждый из вызовов. Instana не сэмплирует вызовы, что позволяет анализировать абсолютно каждый из них. Важной частью мониторинга Java приложений является профилирование кода. У нас могут быть метрики всех компонентов приложения и видимость всех вызовов, и мы можем понять связана ли эта проблема с использованием ресурсов и какой сервис именно пострадал. Но мы не узнаем, какой именно метод в работающем сервисе приложения вызывал проблемы с производительностью. И тут нам поможет постоянно работающий Java профилировщик. Профилировщик покажет точный метод(ы), которые перегружают CPU или имеют большой wait time. Актуальность full-stack мониторинга будет только расти и это обусловлено, в том числе, переходом на микросервисную архитектуру, которая экспоненциально усложняет мониторинг по причине постоянного увеличения подконтрольных компонентов инфраструктуры.  Full-stack мониторинг позволяет охватить весь наш стек – приложения, инфраструктуру, пользователей, и значительно ускорить root-cause анализ и снизить MTTR. Имплементировать full-stack мониторинг возможно и как с помощью подхода “сделай сам”, используя open source стандарты (Prometheus/statsD для сбора метрик, Jaeger/Zipkin для сбора трейсов, jProfiler/Java VisualVM для профилирования Java кода), так и с помощью готовых продуктов, которых на рынке сейчас уже не мало. Будем рады, если в комментариях вы поделитесь применяете ли full-stack мониторинг, как мониторите Java приложения и какие используете для этого инструменты? Микросервисы, DevOps, Observability