Разработка на Java для Kubernetes

Все о разработке высоконагруженных сервисов на Java в распределенных средах на основе Kubernetes и Istio

Прикладной мониторинг Spring Boot сервисов с использованием Micrometer

 

В данной статье речь пойдет о прикладных метриках сервисов или бизнес-метриках. Метрики это статистические показатели работы приложения. Для формирования метрик при работе сервиса, реализованного на Java, существует API из пакета io.micrometer, при этом проекты Java, использующие Spring Boot, имеют возможность интегрироваться с системами накопления метрик (например, Prometheus), а они, в свою очередь, интегрируются с различными сервисами отображения метрик в виде графиков (например, Grafana).

О технических моментах мы поговорим в статье Кодируем отброс метрик в Prometheus, а сейчас рассмотрим метрики концептуально.

По своему назначению метрики можно разделить на прикладные и системные, которым будет посвящен отдельный материал. Если под системными метриками мы понимаем индикаторы состояния инфраструктуры (количество памяти, потоков итд), то под прикладными метрики бизнес-процессов внутри приложения.

Типы метрик в API Micrometer - Counter, Gauge, Timer, Distribution summary

 

Как известно, Micrometer предоставляет 4 типа метрик:

Counter это счетчик. Его значение может только возрастать. С помощью счетчика можно фиксировать, например, коды завершения бизнес-операций:

Код операции

Наименование кода операции

Количество

OK

Успешно

19840

NOT_FOUND

Данные не найдены

81

VALIDATION_ERROR

Запрос не прошел валидацию

22

SYSTEM_ERROR

Системная ошибка

10

 

 

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

Дата/время

Размер очереди документов на обработку

01.11.2022 21:42:00

19

01.11.2022 21:48:00

28

01.11.2022 21:52:00

20

 

Distribution summary используется, когда необходима статистика по частотному распределению значения. Например, если для метрики количества документов, поступивших на обработку, нас интересует как часто количество не превышало 10, 100 и 1000, то нужно использовать данный тип метрики. Если же нас такой показатель не интересует, то правильно использовать Gauge

Размер пакета документов не превышает 10

В 50% случаев

Размер пакета документов не превышает 100

В 80% случаев

Размер пакета документов не превышает 1000

В 95% случаев

Данные из приведенной выше таблицы представляют в виде бакетов (buckets). Допустим, мы уверены, что максимальный размер пакета документов никогда не превысит 10000, а минимальный равен 1. При этом нам для статистики интересно сколько раз размер документов не превышал следующие значения:

1,2,3,… 10,20,…100,200,…1000,2000,…10000

То есть, значения до 10 нас интересуют с шагом 1, до 100 с шагом 10, до 1000 с шагом 100, а до 10000 с шагом 1000. Тогда для каждого значения в последовательности формируется бакет, значением которого будет количество раз, когда фиксируемая величина не превышала данное значение:

				
					operation_packet_size_bucket{business_process="service-back:find-payment",direction="inbound",paramType="out",le="1.0",} 288.0
operation_packet_size_bucket{business_process="service-back:find-payment",direction="inbound",paramType="out",le="2.0",} 616.0
operation_packet_size_bucket{business_process="service-back:find-payment",direction="inbound",paramType="out",le="3.0",} 921.0

...


operation_packet_size_bucket{business_process="service-back:find-payment",direction="inbound",paramType="out",le="60.0",} 3631.0
operation_packet_size_bucket{business_process="service-back:find-payment",direction="inbound",paramType="out",le="70.0",} 3810.0
operation_packet_size_bucket{business_process="service-back:find-payment",direction="inbound",paramType="out",le="80.0",} 3987.0
operation_packet_size_bucket{business_process="service-back:find-payment",direction="inbound",paramType="out",le="90.0",} 4153.0
operation_packet_size_bucket{business_process="service-back:find-payment",direction="inbound",paramType="out",le="100.0",} 4319.0

...

operation_packet_size_bucket{business_process="service-back:find-payment",direction="inbound",paramType="out",le="500.0",} 4754.0
operation_packet_size_bucket{business_process="service-back:find-payment",direction="inbound",paramType="out",le="600.0",} 4850.0
operation_packet_size_bucket{business_process="service-back:find-payment",direction="inbound",paramType="out",le="700.0",} 4959.0
operation_packet_size_bucket{business_process="service-back:find-payment",direction="inbound",paramType="out",le="800.0",} 5075.0
operation_packet_size_bucket{business_process="service-back:find-payment",direction="inbound",paramType="out",le="900.0",} 5187.0
operation_packet_size_bucket{business_process="service-back:find-payment",direction="inbound",paramType="out",le="1000.0",} 5293.0
				
			

 

На основании бакетов можно строить графики, а также бакеты можно использовать для расчета перцентилей, то есть процента случаев, когда показатель метрики укладывается в определенное значение. Например, 95-й перцентиль показывает максимальный размер пакета документов в 95% случаев. Перцентили могут рассчитываться системами сбора метрик (например, Prometheus) на основе бакетов.

Также метрики Distribution summary могут сразу отдавать информацию в виде перцентилей (percentiles).

				
					

operation_packet_size{business_process="service-back:find-payment",direction="inbound",paramType="out",quantile="0.5",} 8.0625
operation_packet_size{business_process="service-back:find-payment",direction="inbound",paramType="out",quantile="0.80",} 92.1728
operation_packet_size{business_process="service-back:find-payment",direction="inbound",paramType="out",quantile="0.95",} 754.8111
				
			

В случае, когда нас интересуют только перцентили (а не бакеты), то предпочтительнее не перегружать трафик и систему сбора метрик их расчетом на основе бакетов, а рассчитывать перцентили в прикладном коде (это делает API Micrometer)

Timer это тоже самое, что Distribution summary, но только для показателя времени. Для программного отброса метрик таймера в API Micrometer и ProjectReactor предусмотрены специальные функции. Наиболее распространенным применением таймера является построение графиков по перцентилям времени выполнения операций

Нетрудно догадаться, что в зависимости от количества показателей предоставляемых метрикой увеличивается количество затрачиваемых на нее ресурсов. Поэтому, правило использования метрик следующее — всегда нужно делать выбор в пользу более простых типов метрик, если их достаточно для получения необходимой статистики. Это значит, что если можно ограничиться Counter, то не следует использовать Gauge. Если не нужны перцентили и частотное распределение значений, то не использовать Distribution summary и Timer.

Типовые прикладные метрики

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

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

При этом сервис get_payments_by_client_front обращается в 3 сервиса:

get_client_info_by_client_backдля получения и возврата дополнительной информации о самом контрагенте

get_payments_by_client_backдля получения данных по платежным документам

get_payment_additional_infoдля получения дополнительной информации о платежах по сделкам с недвижимостью

Таким образом, в функциональности get_payments_by_client_front можно выделить следующие бизнес-процессы (БП):

В данном примере все вложенные бизнес-процессы соответствуют вызовам API других микросервисов, однако в качестве вложенного бизнес-процесса можно выделять и программные блоки внутри одного микросервиса, не вызывающие внешнее API. По каждому такому блоку мы сможем откидывать метрики и получать определенную статистику. Чем больше таких блоков будет, тем точнее мы сможем увидеть информацию на дашбордах и найти узкие места. Например, если бы процесс объединения данных, возвращаемых микросервисами get_payments_by_client_back и get_payment_full_info был нетривиальным, то мы могли бы захотеть отдельно получать статистику по нему.

Время выполнения

Что же за информация нас может интересовать по каждому выделенному бизнес процессу? Ну, во-первых, конечно, это время выполнения. Откидывая метрику времени выполнения каждого бизнес процесса, мы сможем понять какие места в архитектуре больше всего влияют на время ответа и, соответственно, что подлежит оптимизации в первую очередь. Одновременно, это позволит избежать “переоптимизации”, когда мы, не видя всей картинки, слишком усложняем программный код излишней оптимизацией участков, которые существенного влияния на общую картину не имеют. Так, если мы видим, что 90% времени выполнения уходит на внешние вызовы, взаимодействие с инфраструктурой (база данных, брокеры сообщений), то не стоит увлекаться излишней оптимизизацией внутреннего кода сервиса (кэшировать данные, например). Это в итоге не изменит общую статистику, но привнесет дополнительную сложность в алгоритм и увеличит вероятность программной ошибки. С другой стороны, надо быть внимательным и уметь отделять обертку из внутреннего кода от кода самого бизнес-процесса. Например, код получения информации о платежных документах клиента может быть структурирован так:

Очевидно, что если мы будем замерять время между началом БП и окончанием, то в него войдет и внутренний код по маппингу, логированию и откидыванию метрик (речь не о метриках времени, а о других метриках, о которых расскажем ниже). Правильным подходом будет, откидывать либо время блока вызова get_payments_by_client_back, либо откидывать как время блока вызова get_payments_by_client_back, так и время между началом и окончанием БП.

Отдельно следует остановиться на том какой именно статистический показатель времени нас интересует? Если сервис отдает информацию, которая используется пользователями некоего web-сервиса или мобильного приложения, то правильнее оценивать не среднее время ответа, а перцентиль, то есть статистику, которая говорит о том, что в 95 или 99 процентах вызовов информация отдается за время не превышающее определенное значение и далее сравнивать это значение с SLA(соглашение об уровне предоставления услуги), то есть с цифрой, о которой договорились с заказчиком. Если же сервис выполняет некоторые расчеты (например, рассчитывает некоторые статистические показатели) и является частью другого алгоритма, который запускается с определенной периодичностью (например, раз в сутки, раз в месяц), то тут можно ориентироваться на среднее время ответа и не рассчитывать перцентили. В случае использования среднего времени нам достаточно будет метрики типа Gauge, для перцентилей же понадобится Timer.

От того какой показатель времени нас интересует зависит не только выбор типа метрик, а и сама оптимизация кода и инфраструктуры сервиса. Например, разные алгоритмы и настройки сборщика мусора влияют на это. Так, если среднее время отклика важнее перцентиля, с неконкурентным уборщиком мусора в некоторых случаях можно добиться лучших результатов. Подробнее об этом можно прочитать в книге Скотта Оукса Эффективный Java (Тюнинг кода на Java 8 и 11 и дальше)”.

Код результата

Вернемся к метрикам, которые можно связать с бизнес-процессами. Кроме времени выполнения с каждым бизнес-процессом можно ассоциировать результат, который можно назвать кодом завершения бизнес-процесса: успешное выполнение, ошибка БД, ошибка внешнего вызова итд. Код выполнения может быть как один на весь бизнес-процесс, так и в разрезе каждого входного параметра. Например, если процесс получает на вход номера платежей, а возвращает данные по этим платежам, то код завершения операции можно формировать по каждому номеру, а в случае общей ошибки (например, недоступности БД), для каждого номера платежа будет одинаковый код (например, DB_UNAVAILABLE). Для кодов завершения подойдет метрика типа Counter, которая с каждым новом вызовом будет только увеличиваться.

Размер пакета входящих и исходящих параметров

Также с бизнес-процессом можно связать информацию о входящих и исходящих пакетах данных. В приведенном примере нас может заинтересовать среднее количество платежных документов по контрагенту или 95-й перцентиль по количеству платежей (если мы запрашиваем документы за фиксированные периоды — месяц, квартал). В случае процессинговой системы, обрабатывающей платежи пакетами, информация о среднем размере пакета поможет рассчитать скорость обработки одного платежа. В итоге, еще одной типовой метрикой может быть размер входного и выходного пакета, для которого в общем случае понадобится метрика типа DistibutionSummary, чтобы получать не только среднее значение, а перцентили.

Ошибки

Ну, и наконец, важнейшая метрика, относящаяся не к конкретному бизнес процессу, а к микросервису целиком, это информация о произошедших ошибках. Для микросервисов на Java ошибкой можно считать возникновение исключительной ситуации. В этом случае метрика будет отображать количество Exception определенного класса. Поскольку количество может только увеличиваться, то для такой метрики подойдет тип Counter. Кроме технических ошибок (ошибки БД, транспортных протоколов итд), могут возникать и логические ошибки, например, мы можем ожидать, что в случае платежа по сделке с недвижимостью и наличия информации в Росеестре должен существовать документ об уплате пошлины за регистрацию объекта недвижимости, а такого платежа нет. В случае логических ошибок мы также можем им сопоставлять исключительные ситуации и отбрасывать информацию о них в метрику. Также неплохо было бы выделить 2 разновидности ошибок — ошибки уровня ERROR и уровня WARNING. Уровень ERROR требует немедленного разбора, в то время как WARNING может возникать при нормальной работе микросервиса. Если на дашборде во время работы микросервиса увидели ошибку типа ERROR и по типу (классу) ошибки непонятна причина ее возникновения (например, возник NullPointerException), то необходимо скачать логи за несколько минут назад и вперед от времени возникновения ошибки на дашборде и по логам проанализировать ситуацию (разумеется, ошибка должна быть залогирована и не только залогирована, а как-то связана с контекстом выполнения, например, кодом контрагента, номером платежа). Как пример, NullPointerException мог возникнуть, если для платежа определенного типа мы ожидаем обязательность некоторого атрибута, а он не заполнен.

Подведем итоги

1) Метрики это статистические показатели работы приложения, для формирования которых существует Java API Micrometer. Есть 4 типа метрик — счетчики (Counter), датчики (Gauge), таймер (Timer) и сводки по распределению (Distibution summary). Важно, что метрики не фиксируют конкретные ситуации, связанные с определенными данными. Например, по метрикам мы не сможем понять обработка данных по какому клиенту или платежу привела к ошибке или замедлению работы. Для этого существуют логи и трассировочные дашборды.

2) Время выполнения отдельных операций можно фиксировать двумя способами — в виде среднего значения или перцентиля (то есть, статистики, которая говорит о том, что 90,95 или 99 процентов операций данного типа выполняется не более чем за N миллисекунд). Обычно, для систем, взаимодействующих с внешними потребителями, важен именно перцентиль, а не среднее время выполнения. Для расчета перцентилей необходима метрика типа Timer, в то время как для расчета среднего времени достаточно метрики типа Gauge.

3) Метрики можно логически разделить на системные и прикладные. Системные метрики фиксируют техническую составляющую работы приложения, в то время как прикладные его бизнес составляющую

4) Подход к фиксации прикладных метрик можно стандартизировать, разбив весь функционал на бизнес-процессы и привязав к каждому бизнес-процессу время выполнения, код результата выполнения бизнес-процесса и, в некоторых случаях, размер входных и выходных данных бизнес-процесса

5) Приложение должно обязательно фиксировать в метриках ошибки, возникающие в процессе его работы. Все ошибки можно представить в виде исключительных ситуаций, некоторые и которых должно генерировать само приложение, если обнаруживается какая-либо логическая ошибка в алгоритме или данных, другие возникают сами при ошибках разработчика, недоступности инфраструктуры и проч. Ошибки целесообразно разделить на ERROR и WARNING. Первый тип это критические ошибки, требующие немедленного разбора.

Поделиться

LEAVE A REPLYYour email address will not be published. Required fields are marked *Your Name

Copyright © 2025 Разработка на Java для Kubernetes